From b9f406dcaaff2b631b2a1b444e6c20b445719339 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 25 Sep 2024 20:52:02 +0200 Subject: [PATCH] wasm gc: general implementation of System.arraycopy --- .../wasm/disasm/DisassemblyCodeListener.java | 9 + .../gc/SystemArrayCopyDependencySupport.java | 37 ++++ .../backend/wasm/gc/WasmGCDependencies.java | 1 + .../gc/classes/WasmGCClassGenerator.java | 80 ++++++++- .../gc/classes/WasmGCClassInfoProvider.java | 2 + .../WasmGCSupertypeFunctionGenerator.java | 30 ++-- .../gc/methods/WasmGCGenerationVisitor.java | 6 + .../gc/SystemArrayCopyIntrinsic.java | 164 ++++++++++++++++-- .../intrinsics/gc/WasmGCIntrinsicContext.java | 3 + .../wasm/intrinsics/gc/WasmGCIntrinsics.java | 5 +- .../wasm/model/expression/WasmStructSet.java | 9 + .../gc/BaseClassesTransformation.java | 11 ++ .../analysis/ClassMetadataRequirements.java | 24 +++ 13 files changed, 353 insertions(+), 28 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/SystemArrayCopyDependencySupport.java diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java index 44f17a793..2fe2cc975 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java @@ -870,6 +870,15 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements writer.eol(); } + @Override + public void arrayCopy(int targetTypeIndex, int sourceTypeIndex) { + writer.address().write("array.copy "); + writeTypeRef(targetTypeIndex); + writer.write(" "); + writeTypeRef(sourceTypeIndex); + writer.eol(); + } + @Override public void functionReference(int functionIndex) { writer.address().write("ref.func "); diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/SystemArrayCopyDependencySupport.java b/core/src/main/java/org/teavm/backend/wasm/gc/SystemArrayCopyDependencySupport.java new file mode 100644 index 000000000..d87c8777c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/SystemArrayCopyDependencySupport.java @@ -0,0 +1,37 @@ +/* + * 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.gc; + +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.MethodDependency; +import org.teavm.model.MethodReference; + +public class SystemArrayCopyDependencySupport extends AbstractDependencyListener { + private static final MethodReference COPY_METHOD = new MethodReference(System.class, + "arraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class); + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getMethod().getReference().equals(COPY_METHOD)) { + var implMethod = agent.linkMethod(new MethodReference(System.class, + "arrayCopyImpl", Object.class, int.class, Object.class, int.class, int.class, void.class)); + method.getVariable(1).connect(implMethod.getVariable(1)); + method.getVariable(3).connect(implMethod.getVariable(3)); + implMethod.use(); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java index 5582c526f..7434eb9e9 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java @@ -38,6 +38,7 @@ public class WasmGCDependencies { contributeString(); analyzer.addDependencyListener(new WasmGCReferenceQueueDependency()); analyzer.addDependencyListener(new WasmGCResourceDependency()); + analyzer.addDependencyListener(new SystemArrayCopyDependencySupport()); } public void contributeStandardExports() { 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 ecd4a2ae5..b29155d1c 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 @@ -145,13 +145,16 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private int enumConstantsFunctionOffset; private int arrayLengthOffset = -1; private int arrayGetOffset = -1; + private int arrayCopyOffset = -1; private int cloneOffset = -1; private int servicesOffset = -1; private WasmStructure arrayVirtualTableStruct; private WasmFunction arrayGetObjectFunction; private WasmFunction arrayLengthObjectFunction; + private WasmFunction arrayCopyObjectFunction; private WasmFunctionType arrayGetType; private WasmFunctionType arrayLengthType; + private WasmFunctionType arrayCopyType; private List nonInitializedStructures = new ArrayList<>(); private WasmArray objectArrayType; private boolean hasLoadServices; @@ -176,7 +179,8 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit standardClasses = new WasmGCStandardClasses(this); strings = new WasmGCStringPool(standardClasses, module, functionProvider, names, functionTypes, dependencyInfo); - supertypeGenerator = new WasmGCSupertypeFunctionGenerator(module, this, names, tagRegistry, functionTypes); + supertypeGenerator = new WasmGCSupertypeFunctionGenerator(module, this, names, tagRegistry, functionTypes, + queue); newArrayGenerator = new WasmGCNewArrayFunctionGenerator(module, functionTypes, this, names, queue); typeMapper = new WasmGCTypeMapper(classSource, this, functionTypes, module); var customTypeMapperFactoryContext = customTypeMapperFactoryContext(); @@ -664,6 +668,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit WasmStructure objectStructure) { var virtualTable = virtualTables.lookup("java.lang.Object"); var structure = getArrayVirtualTableStructure(); + structure.init(); var itemType = ((ValueType.Array) type).getItemType(); for (var entry : virtualTable.getEntries()) { @@ -681,9 +686,13 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit target.add(new WasmStructSet(structure, new WasmGetGlobal(global), arrayGetOffset, new WasmFunctionReference(getFunction))); } + if (info.arrayCopy()) { + var copyFunction = getArrayCopyFunction(itemType); + target.add(new WasmStructSet(structure, new WasmGetGlobal(global), arrayCopyOffset, + new WasmFunctionReference(copyFunction))); + } } - private WasmFunction getArrayLengthFunction(WasmStructure objectStructure) { var arrayTypeRef = (WasmType.CompositeReference) objectStructure.getFields().get(ARRAY_DATA_FIELD_OFFSET) .getUnpackedType(); @@ -815,6 +824,53 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit return function; } + private WasmFunction getArrayCopyFunction(ValueType itemType) { + if (itemType instanceof ValueType.Primitive) { + return createArrayCopyFunction(itemType); + } + return getArrayCopyObjectFunction(); + } + + private WasmFunction getArrayCopyObjectFunction() { + if (arrayCopyObjectFunction == null) { + arrayCopyObjectFunction = createArrayCopyFunction(OBJECT_TYPE); + } + return arrayCopyObjectFunction; + } + + private WasmFunction createArrayCopyFunction(ValueType type) { + var function = new WasmFunction(getArrayCopyType()); + function.setName(names.topLevel("Array<" + names.suggestForType(type) + ">::copy")); + module.functions.add(function); + function.setReferenced(true); + + var arrayStruct = getClassInfo(ValueType.arrayOf(type)).structure; + var arrayDataTypeRef = (WasmType.CompositeReference) arrayStruct.getFields() + .get(ARRAY_DATA_FIELD_OFFSET).getUnpackedType(); + var arrayDataType = (WasmArray) arrayDataTypeRef.composite; + var sourceLocal = new WasmLocal(standardClasses.objectClass().getType(), "source"); + var sourceIndexLocal = new WasmLocal(WasmType.INT32, "sourceIndex"); + var targetLocal = new WasmLocal(standardClasses.objectClass().getType(), "target"); + var targetIndexLocal = new WasmLocal(WasmType.INT32, "targetIndex"); + var countLocal = new WasmLocal(WasmType.INT32, "count"); + function.add(sourceLocal); + function.add(sourceIndexLocal); + function.add(targetLocal); + function.add(targetIndexLocal); + function.add(countLocal); + + var sourceArray = new WasmCast(new WasmGetLocal(sourceLocal), arrayStruct.getNonNullReference()); + var sourceArrayData = new WasmStructGet(arrayStruct, sourceArray, ARRAY_DATA_FIELD_OFFSET); + var targetArray = new WasmCast(new WasmGetLocal(targetLocal), arrayStruct.getNonNullReference()); + var targetArrayData = new WasmStructGet(arrayStruct, targetArray, ARRAY_DATA_FIELD_OFFSET); + + function.getBody().add(new WasmArrayCopy( + arrayDataType, targetArrayData, new WasmGetLocal(sourceIndexLocal), + arrayDataType, sourceArrayData, new WasmGetLocal(targetIndexLocal), + new WasmGetLocal(countLocal))); + return function; + } + private WasmFunctionType getArrayGetType() { if (arrayGetType == null) { arrayGetType = functionTypes.of(standardClasses.objectClass().getType(), @@ -830,6 +886,14 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit return arrayLengthType; } + private WasmFunctionType getArrayCopyType() { + if (arrayCopyType == null) { + arrayCopyType = functionTypes.of(null, standardClasses.objectClass().getType(), WasmType.INT32, + standardClasses.objectClass().getType(), WasmType.INT32, WasmType.INT32); + } + return arrayCopyType; + } + private void fillVirtualTableEntry(List target, WasmGlobal global, WasmStructure structure, WasmGCVirtualTable virtualTable, WasmGCVirtualTableEntry entry) { var implementor = virtualTable.implementor(entry); @@ -966,6 +1030,12 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit fields.add(new WasmField(arrayGetType.getReference().asStorage(), names.structureField("@arrayGet"))); } + if (metadataRequirements.hasArrayCopy()) { + arrayCopyOffset = fields.size(); + var arrayCopyType = getArrayCopyType(); + fields.add(new WasmField(arrayCopyType.getReference().asStorage(), + names.structureField("@arrayCopy"))); + } }); arrayVirtualTableStruct.setSupertype(standardClasses.objectClass().getVirtualTableStructure()); module.types.add(arrayVirtualTableStruct); @@ -986,6 +1056,12 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit return arrayGetOffset; } + @Override + public int getArrayCopyOffset() { + initStructures(); + return arrayCopyOffset; + } + @Override public int getEnumConstantsFunctionOffset() { return enumConstantsFunctionOffset; 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 45bb6703b..f43243a01 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 @@ -65,6 +65,8 @@ public interface WasmGCClassInfoProvider { int getArrayLengthOffset(); + int getArrayCopyOffset(); + int getEnumConstantsFunctionOffset(); int getCloneOffset(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCSupertypeFunctionGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCSupertypeFunctionGenerator.java index 316368a32..9a2d157fe 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCSupertypeFunctionGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCSupertypeFunctionGenerator.java @@ -19,6 +19,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Queue; import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.model.WasmFunction; @@ -51,19 +52,22 @@ public class WasmGCSupertypeFunctionGenerator implements WasmGCSupertypeFunction private TagRegistry tagRegistry; private WasmFunctionTypes functionTypes; private WasmFunctionType functionType; + private Queue queue; WasmGCSupertypeFunctionGenerator( WasmModule module, WasmGCClassGenerator classGenerator, WasmGCNameProvider nameProvider, TagRegistry tagRegistry, - WasmFunctionTypes functionTypes + WasmFunctionTypes functionTypes, + Queue queue ) { this.module = module; this.classGenerator = classGenerator; this.nameProvider = nameProvider; this.tagRegistry = tagRegistry; this.functionTypes = functionTypes; + this.queue = queue; } @Override @@ -83,17 +87,19 @@ public class WasmGCSupertypeFunctionGenerator implements WasmGCSupertypeFunction function.add(subtypeVar); module.functions.add(function); - if (type instanceof ValueType.Object) { - var className = ((ValueType.Object) type).getClassName(); - generateIsClass(subtypeVar, className, function); - } else if (type instanceof ValueType.Array) { - ValueType itemType = ((ValueType.Array) type).getItemType(); - generateIsArray(subtypeVar, itemType, function.getBody()); - } else { - var expected = classGenerator.getClassInfo(type).pointer; - var condition = new WasmReferencesEqual(new WasmGetLocal(subtypeVar), new WasmGetGlobal(expected)); - function.getBody().add(condition); - } + queue.add(() -> { + if (type instanceof ValueType.Object) { + var className = ((ValueType.Object) type).getClassName(); + generateIsClass(subtypeVar, className, function); + } else if (type instanceof ValueType.Array) { + ValueType itemType = ((ValueType.Array) type).getItemType(); + generateIsArray(subtypeVar, itemType, function.getBody()); + } else { + var expected = classGenerator.getClassInfo(type).pointer; + var condition = new WasmReferencesEqual(new WasmGetLocal(subtypeVar), new WasmGetGlobal(expected)); + function.getBody().add(condition); + } + }); return function; } 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 a89594ce0..6b15f0b5b 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 @@ -48,6 +48,7 @@ import org.teavm.backend.wasm.model.WasmFunctionType; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmStructure; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmArrayGet; import org.teavm.backend.wasm.model.expression.WasmArrayLength; @@ -822,5 +823,10 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { public WasmGCStringProvider strings() { return context.strings(); } + + @Override + public WasmTag exceptionTag() { + return context.getExceptionTag(); + } }; } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/SystemArrayCopyIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/SystemArrayCopyIntrinsic.java index a8d0e2cb9..657ffd933 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/SystemArrayCopyIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/SystemArrayCopyIntrinsic.java @@ -19,20 +19,47 @@ import org.teavm.ast.InvocationExpr; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.model.WasmArray; import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmFunctionType; +import org.teavm.backend.wasm.model.WasmLocal; 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.WasmBlock; +import org.teavm.backend.wasm.model.expression.WasmBranch; 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.WasmGetLocal; +import org.teavm.backend.wasm.model.expression.WasmInt32Constant; +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.WasmReturn; import org.teavm.backend.wasm.model.expression.WasmStructGet; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; public class SystemArrayCopyIntrinsic implements WasmGCIntrinsic { private WasmFunction defaultFunction; + private WasmFunction argsCheckFunction; @Override public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + switch (invocation.getMethod().getName()) { + case "arraycopy": + return generateArrayCopy(invocation, context); + case "doArrayCopy": + return generateDoArrayCopy(invocation, context); + default: + throw new IllegalArgumentException(); + } + } + + private WasmExpression generateArrayCopy(InvocationExpr invocation, WasmGCIntrinsicContext context) { var result = tryGenerateSpecialCase(invocation, context); if (result == null) { tryGenerateSpecialCase(invocation, context); @@ -46,6 +73,32 @@ public class SystemArrayCopyIntrinsic implements WasmGCIntrinsic { return result; } + private WasmExpression generateDoArrayCopy(InvocationExpr invocation, WasmGCIntrinsicContext context) { + var objStruct = context.classInfoProvider().getClassInfo(Object.class.getName()).getStructure(); + var arrayClsStruct = context.classInfoProvider().getArrayVirtualTableStructure(); + var block = new WasmBlock(false); + + var source = context.exprCache().create(context.generate(invocation.getArguments().get(0)), + objStruct.getReference(), invocation.getLocation(), block.getBody()); + WasmExpression sourceCls = new WasmStructGet(objStruct, source.expr(), + WasmGCClassInfoProvider.CLASS_FIELD_OFFSET); + sourceCls = new WasmCast(sourceCls, arrayClsStruct.getNonNullReference()); + var copyFunction = new WasmStructGet(arrayClsStruct, sourceCls, + context.classInfoProvider().getArrayCopyOffset()); + var functionTypeRef = (WasmType.CompositeReference) arrayClsStruct.getFields().get( + context.classInfoProvider().getArrayCopyOffset()).getUnpackedType(); + var functionType = (WasmFunctionType) functionTypeRef.composite; + var call = new WasmCallReference(copyFunction, functionType); + call.getArguments().add(source.expr()); + call.getArguments().add(context.generate(invocation.getArguments().get(1))); + call.getArguments().add(context.generate(invocation.getArguments().get(2))); + call.getArguments().add(context.generate(invocation.getArguments().get(3))); + call.getArguments().add(context.generate(invocation.getArguments().get(4))); + block.getBody().add(call); + source.release(); + return block; + } + private WasmExpression tryGenerateSpecialCase(InvocationExpr invocation, WasmGCIntrinsicContext context) { var sourceArray = invocation.getArguments().get(0); var targetArray = invocation.getArguments().get(2); @@ -69,35 +122,120 @@ public class SystemArrayCopyIntrinsic implements WasmGCIntrinsic { return null; } + var block = new WasmBlock(false); + var wasmTargetArrayType = (WasmType.CompositeReference) context.typeMapper().mapType( ValueType.arrayOf(targetItemType)); var wasmTargetArrayStruct = (WasmStructure) wasmTargetArrayType.composite; var wasmTargetArrayWrapper = context.generate(invocation.getArguments().get(2)); - var wasmTargetArray = new WasmStructGet(wasmTargetArrayStruct, wasmTargetArrayWrapper, - WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET); - var wasmTargetIndex = context.generate(invocation.getArguments().get(3)); + var wasmTargetArrayTypeRef = (WasmType.CompositeReference) wasmTargetArrayStruct.getFields() + .get(WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET).getUnpackedType(); + var wasmTargetArray = context.exprCache().create(new WasmStructGet(wasmTargetArrayStruct, + wasmTargetArrayWrapper, WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET), + wasmTargetArrayTypeRef, null, block.getBody()); + var wasmTargetIndex = context.exprCache().create(context.generate(invocation.getArguments().get(3)), + WasmType.INT32, null, block.getBody()); var wasmSourceArrayType = (WasmType.CompositeReference) context.typeMapper().mapType( ValueType.arrayOf(sourceItemType)); var wasmSourceArrayStruct = (WasmStructure) wasmSourceArrayType.composite; var wasmSourceArrayWrapper = context.generate(invocation.getArguments().get(0)); - var wasmSourceArray = new WasmStructGet(wasmSourceArrayStruct, wasmSourceArrayWrapper, - WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET); - var wasmSourceIndex = context.generate(invocation.getArguments().get(1)); - var wasmSize = context.generate(invocation.getArguments().get(4)); - - var wasmTargetArrayTypeRef = (WasmType.CompositeReference) wasmTargetArrayStruct.getFields() - .get(WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET).getUnpackedType(); var wasmSourceArrayTypeRef = (WasmType.CompositeReference) wasmSourceArrayStruct.getFields() .get(WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET).getUnpackedType(); + var wasmSourceArray = context.exprCache().create(new WasmStructGet(wasmSourceArrayStruct, + wasmSourceArrayWrapper, WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET), + wasmSourceArrayTypeRef, null, block.getBody()); + var wasmSourceIndex = context.exprCache().create(context.generate(invocation.getArguments().get(1)), + WasmType.INT32, null, block.getBody()); + var wasmSize = context.exprCache().create(context.generate(invocation.getArguments().get(4)), + WasmType.INT32, null, block.getBody()); - return new WasmArrayCopy((WasmArray) wasmTargetArrayTypeRef.composite, wasmTargetArray, wasmTargetIndex, - (WasmArray) wasmSourceArrayTypeRef.composite, wasmSourceArray, wasmSourceIndex, wasmSize); + + block.getBody().add(new WasmCall( + getArgsCheckFunction(context), + wasmTargetArray.expr(), wasmTargetIndex.expr(), + wasmSourceArray.expr(), wasmSourceIndex.expr(), + wasmSize.expr() + )); + + block.getBody().add(new WasmArrayCopy( + (WasmArray) wasmTargetArrayTypeRef.composite, wasmTargetArray.expr(), wasmTargetIndex.expr(), + (WasmArray) wasmSourceArrayTypeRef.composite, wasmSourceArray.expr(), wasmSourceIndex.expr(), + wasmSize.expr() + )); + wasmTargetArray.release(); + wasmTargetIndex.release(); + wasmSourceArray.release(); + wasmSourceIndex.release(); + wasmSize.release(); + return block; + } + + private WasmFunction getArgsCheckFunction(WasmGCIntrinsicContext context) { + if (argsCheckFunction == null) { + argsCheckFunction = createArgsCheckFunction(context); + } + return argsCheckFunction; + } + + private WasmFunction createArgsCheckFunction(WasmGCIntrinsicContext context) { + var function = new WasmFunction(context.functionTypes().of(null, + WasmType.Reference.ARRAY, WasmType.INT32, WasmType.Reference.ARRAY, + WasmType.INT32, WasmType.INT32)); + function.setName(context.names().topLevel("teavm@checkArrayCopy")); + context.module().functions.add(function); + + var targetArrayLocal = new WasmLocal(WasmType.Reference.ARRAY, "targetArray"); + var targetArrayIndexLocal = new WasmLocal(WasmType.INT32, "targetIndex"); + var sourceArrayLocal = new WasmLocal(WasmType.Reference.ARRAY, "sourceArray"); + var sourceArrayIndexLocal = new WasmLocal(WasmType.INT32, "sourceIndex"); + var countLocal = new WasmLocal(WasmType.INT32, "count"); + function.add(targetArrayLocal); + function.add(targetArrayIndexLocal); + function.add(sourceArrayLocal); + function.add(sourceArrayIndexLocal); + function.add(countLocal); + + var block = new WasmBlock(false); + var targetIndexLessThanZero = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_SIGNED, + new WasmGetLocal(targetArrayIndexLocal), new WasmInt32Constant(0)); + block.getBody().add(new WasmBranch(targetIndexLessThanZero, block)); + var sourceIndexLessThanZero = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_SIGNED, + new WasmGetLocal(sourceArrayIndexLocal), new WasmInt32Constant(0)); + block.getBody().add(new WasmBranch(sourceIndexLessThanZero, block)); + var countPositive = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_SIGNED, + new WasmGetLocal(countLocal), new WasmInt32Constant(0)); + block.getBody().add(new WasmBranch(countPositive, block)); + + var targetSize = new WasmArrayLength(new WasmGetLocal(targetArrayLocal)); + var targetIndexLimit = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SUB, + targetSize, new WasmGetLocal(countLocal)); + var targetIndexGreaterThanSize = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.GT_SIGNED, + new WasmGetLocal(targetArrayIndexLocal), targetIndexLimit); + block.getBody().add(new WasmBranch(targetIndexGreaterThanSize, block)); + + var sourceSize = new WasmArrayLength(new WasmGetLocal(sourceArrayLocal)); + var sourceIndexLimit = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SUB, + sourceSize, new WasmGetLocal(countLocal)); + var sourceIndexGreaterThanSize = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.GT_SIGNED, + new WasmGetLocal(sourceArrayIndexLocal), sourceIndexLimit); + block.getBody().add(new WasmBranch(sourceIndexGreaterThanSize, block)); + + block.getBody().add(new WasmReturn()); + + function.getBody().add(block); + + var aioobeFunction = context.functions().forStaticMethod(new MethodReference(WasmGCSupport.class, "aiiobe", + ArrayIndexOutOfBoundsException.class)); + var throwExpr = new WasmThrow(context.exceptionTag()); + throwExpr.getArguments().add(new WasmCall(aioobeFunction)); + function.getBody().add(throwExpr); + return function; } private WasmFunction getDefaultFunction(WasmGCIntrinsicContext manager) { if (defaultFunction == null) { defaultFunction = manager.functions().forStaticMethod(new MethodReference(System.class, - "arraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class)); + "arrayCopyImpl", Object.class, int.class, Object.class, int.class, int.class, void.class)); } return defaultFunction; } 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 0f7ca7422..1574c989a 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 @@ -26,6 +26,7 @@ import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; import org.teavm.backend.wasm.model.WasmModule; +import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.model.ClassHierarchy; @@ -55,4 +56,6 @@ public interface WasmGCIntrinsicContext { WasmGCStringProvider strings(); ClassLoader classLoader(); + + WasmTag exceptionTag(); } 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 c1646e76b..00919cf13 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 @@ -108,8 +108,11 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { } private void fillSystem() { + var arrayCopyIntrinsic = new SystemArrayCopyIntrinsic(); add(new MethodReference(System.class, "arraycopy", Object.class, int.class, Object.class, - int.class, int.class, void.class), new SystemArrayCopyIntrinsic()); + int.class, int.class, void.class), arrayCopyIntrinsic); + add(new MethodReference(System.class, "doArrayCopy", Object.class, int.class, Object.class, + int.class, int.class, void.class), arrayCopyIntrinsic); add(new MethodReference(System.class, "currentTimeMillis", long.class), new SystemIntrinsic()); } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmStructSet.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmStructSet.java index c3ad5b180..7d956f33c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmStructSet.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmStructSet.java @@ -25,6 +25,7 @@ public class WasmStructSet extends WasmExpression { private WasmExpression value; public WasmStructSet(WasmStructure type, WasmExpression instance, int fieldIndex, WasmExpression value) { + checkFieldIndex(fieldIndex); this.type = Objects.requireNonNull(type); this.instance = Objects.requireNonNull(instance); this.fieldIndex = fieldIndex; @@ -52,6 +53,7 @@ public class WasmStructSet extends WasmExpression { } public void setFieldIndex(int fieldIndex) { + checkFieldIndex(fieldIndex); this.fieldIndex = fieldIndex; } @@ -67,4 +69,11 @@ public class WasmStructSet extends WasmExpression { public void acceptVisitor(WasmExpressionVisitor visitor) { visitor.visit(this); } + + + private static void checkFieldIndex(int fieldIndex) { + if (fieldIndex < 0) { + throw new IllegalArgumentException("Field index must be >= 0"); + } + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java index 1a9fd4e05..6b24229c5 100644 --- a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java @@ -83,11 +83,15 @@ public class BaseClassesTransformation implements ClassHolderTransformer { } } } else if (cls.getName().equals("java.lang.System")) { + Program arrayCopyProgram = null; + MethodDescriptor arrayCopyDescriptor = null; for (var method : cls.getMethods()) { switch (method.getName()) { case "arraycopy": + arrayCopyProgram = method.getProgram(); method.setProgram(null); method.getModifiers().add(ElementModifier.NATIVE); + arrayCopyDescriptor = method.getDescriptor(); break; case "currentTimeMillis": { var annotation = new AnnotationHolder(Import.class.getName()); @@ -97,6 +101,13 @@ public class BaseClassesTransformation implements ClassHolderTransformer { } } } + if (arrayCopyProgram != null) { + var arrayCopyImpl = new MethodHolder(new MethodDescriptor("arrayCopyImpl", + arrayCopyDescriptor.getSignature())); + arrayCopyImpl.setProgram(arrayCopyProgram); + arrayCopyImpl.getModifiers().add(ElementModifier.STATIC); + cls.addMethod(arrayCopyImpl); + } } else if (cls.getName().equals("java.lang.ref.WeakReference")) { var constructor = cls.getMethod(new MethodDescriptor("", Object.class, ReferenceQueue.class, void.class)); 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 a29885f43..fbe9ce251 100644 --- a/core/src/main/java/org/teavm/model/analysis/ClassMetadataRequirements.java +++ b/core/src/main/java/org/teavm/model/analysis/ClassMetadataRequirements.java @@ -42,10 +42,13 @@ public class ClassMetadataRequirements { "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 MethodReference ARRAY_COPY = new MethodReference(System.class, + "arraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class); private static final ClassInfo EMPTY_INFO = new ClassInfo(); private Map requirements = new HashMap<>(); private boolean hasArrayGet; private boolean hasArrayLength; + private boolean hasArrayCopy; private boolean hasEnumConstants; private boolean hasSuperclass; private boolean hasIsAssignable; @@ -137,6 +140,15 @@ public class ClassMetadataRequirements { } } + var arrayCopy = dependencyInfo.getMethod(ARRAY_COPY); + if (arrayCopy != null) { + hasArrayCopy = arrayCopy.isUsed(); + var classNames = arrayCopy.getVariable(1).getTypes(); + for (var className : classNames) { + requirements.computeIfAbsent(decodeType(className), k -> new ClassInfo()).arrayCopy = true; + } + } + var clone = dependencyInfo.getMethod(new MethodReference(Object.class, "cloneObject", Object.class)); if (clone != null) { var classNames = clone.getVariable(0).getTypes(); @@ -182,6 +194,10 @@ public class ClassMetadataRequirements { return hasArrayLength; } + public boolean hasArrayCopy() { + return hasArrayCopy; + } + public boolean hasEnumConstants() { return hasEnumConstants; } @@ -240,6 +256,7 @@ public class ClassMetadataRequirements { boolean newArray; boolean arrayLength; boolean arrayGet; + boolean arrayCopy; boolean cloneMethod; boolean enumConstants; @@ -283,6 +300,11 @@ public class ClassMetadataRequirements { return arrayLength; } + @Override + public boolean arrayCopy() { + return arrayCopy; + } + @Override public boolean arrayGet() { return arrayGet; @@ -318,6 +340,8 @@ public class ClassMetadataRequirements { boolean arrayGet(); + boolean arrayCopy(); + boolean cloneMethod(); boolean enumConstants();