From 61eb666503ee5e11fd378efe8ae32f821bd189b1 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 19 Jul 2023 21:11:27 +0200 Subject: [PATCH] classlib: faster implementation of System.arraycopy --- .../java/lang/SystemNativeGenerator.java | 2 +- .../org/teavm/classlib/java/lang/TSystem.java | 11 +- .../MethodOptimizationContext.java | 3 + .../SystemArrayCopyOptimization.java | 250 ++++++++++++++++++ .../teavm/vm/StdlibDependencyListener.java | 37 +++ core/src/main/java/org/teavm/vm/TeaVM.java | 8 + .../RepeatedFieldReadEliminationTest.java | 6 + .../test/ScalarReplacementTest.java | 6 + 8 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/teavm/model/optimization/SystemArrayCopyOptimization.java create mode 100644 core/src/main/java/org/teavm/vm/StdlibDependencyListener.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java index 74c81629b..f8d6ea108 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java @@ -57,7 +57,7 @@ public class SystemNativeGenerator implements Generator, DependencyPlugin { writer.append("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine(); writer.append(dest + ".data[" + destPos + "++] = " + src + ".data[" + srcPos + "++];").softNewLine(); writer.outdent().append("}").softNewLine(); - writer.outdent().append("} else {").indent().softNewLine(); + writer.outdent().append("}").ws().append("else").ws().append("{").indent().softNewLine(); writer.append(srcPos + " = (" + srcPos + " + " + length + ") | 0;").softNewLine(); writer.append(destPos + " = (" + destPos + " + " + length + ") | 0;").softNewLine(); writer.append("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine(); diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java index 906f77cf8..45fc19cac 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java @@ -15,6 +15,7 @@ */ package org.teavm.classlib.java.lang; +import java.lang.reflect.Array; import java.util.Enumeration; import java.util.Properties; import org.teavm.backend.c.intrinsic.RuntimeInclude; @@ -115,10 +116,18 @@ public final class TSystem extends TObject { doArrayCopy(src, srcPos, dest, destPos, length); } + static void fastArraycopy(Object src, int srcPos, Object dest, int destPos, int length) { + if (srcPos < 0 || destPos < 0 || length < 0 || srcPos + length > Array.getLength(src) + || destPos + length > Array.getLength(dest)) { + throw new TIndexOutOfBoundsException(); + } + doArrayCopy(src, srcPos, dest, destPos, length); + } + @GeneratedBy(SystemNativeGenerator.class) @DelegateTo("doArrayCopyLowLevel") @NoSideEffects - private static native void doArrayCopy(Object src, int srcPos, Object dest, int destPos, int length); + static native void doArrayCopy(Object src, int srcPos, Object dest, int destPos, int length); @Unmanaged static void doArrayCopyLowLevel(RuntimeArray src, int srcPos, RuntimeArray dest, int destPos, int length) { diff --git a/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java b/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java index 0151f6e2c..c62ce2098 100644 --- a/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java +++ b/core/src/main/java/org/teavm/model/optimization/MethodOptimizationContext.java @@ -16,6 +16,7 @@ package org.teavm.model.optimization; import org.teavm.dependency.DependencyInfo; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReader; @@ -25,4 +26,6 @@ public interface MethodOptimizationContext { DependencyInfo getDependencyInfo(); ClassReaderSource getClassSource(); + + ClassHierarchy getHierarchy(); } diff --git a/core/src/main/java/org/teavm/model/optimization/SystemArrayCopyOptimization.java b/core/src/main/java/org/teavm/model/optimization/SystemArrayCopyOptimization.java new file mode 100644 index 000000000..9f83bb7ba --- /dev/null +++ b/core/src/main/java/org/teavm/model/optimization/SystemArrayCopyOptimization.java @@ -0,0 +1,250 @@ +/* + * Copyright 2023 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.model.optimization; + +import com.carrotsearch.hppc.IntHashSet; +import com.carrotsearch.hppc.IntStack; +import java.util.Arrays; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.ConstructArrayInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; +import org.teavm.model.instructions.GetElementInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.NullCheckInstruction; +import org.teavm.model.instructions.UnwrapArrayInstruction; + +public class SystemArrayCopyOptimization implements MethodOptimization { + private static final MethodReference ARRAY_COPY_METHOD = new MethodReference(System.class, + "arraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class); + private static final MethodReference FAST_ARRAY_COPY_METHOD = new MethodReference(System.class, + "fastArraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class); + + @Override + public boolean optimize(MethodOptimizationContext context, Program program) { + TypeInference typeInference = null; + var somethingChanged = false; + for (var block : program.getBasicBlocks()) { + for (var instruction : block) { + if (instruction instanceof InvokeInstruction) { + var invoke = (InvokeInstruction) instruction; + var method = invoke.getMethod(); + if (method.equals(ARRAY_COPY_METHOD)) { + if (typeInference == null) { + typeInference = new TypeInference(program, context.getMethod().getDescriptor()); + } + var sourceType = typeInference.typeOf(invoke.getArguments().get(0)); + var destType = typeInference.typeOf(invoke.getArguments().get(2)); + if (sourceType != null && destType != null) { + if (sourceType.equals(destType) + || context.getHierarchy().isSuperType(destType, sourceType, false)) { + invoke.setMethod(FAST_ARRAY_COPY_METHOD); + somethingChanged = true; + } + } + } + } + } + } + return somethingChanged; + } + + private static class TypeInference { + private ValueType[] types; + private int[] assignments; + private int[] elementAssignments; + private int[][] phis; + private boolean[] present; + private boolean[] calculating; + + TypeInference(Program program, MethodDescriptor descriptor) { + types = new ValueType[program.variableCount()]; + assignments = new int[program.variableCount()]; + elementAssignments = new int[program.variableCount()]; + phis = new int[program.variableCount()][]; + Arrays.fill(assignments, -1); + Arrays.fill(elementAssignments, -1); + present = new boolean[program.variableCount()]; + calculating = new boolean[program.variableCount()]; + + var visitor = new InitialTypeVisitor(types, assignments, elementAssignments); + var params = Math.min(descriptor.parameterCount(), program.variableCount() - 1); + for (var i = 0; i < params; ++i) { + visitor.type(program.variableAt(i + 1), descriptor.parameterType(i)); + } + for (var block : program.getBasicBlocks()) { + for (var insn : block) { + insn.acceptVisitor(visitor); + } + for (var phi : block.getPhis()) { + var sourceIndexes = new IntHashSet(); + for (var incoming : phi.getIncomings()) { + sourceIndexes.add(incoming.getValue().getIndex()); + } + var inputs = sourceIndexes.toArray(); + Arrays.sort(inputs); + phis[phi.getReceiver().getIndex()] = inputs; + } + } + + for (int i = 0; i < types.length; ++i) { + if (types[i] != null) { + present[i] = true; + } + } + } + + ValueType typeOf(Variable variable) { + if (!present[variable.getIndex()]) { + calculate(variable.getIndex()); + } + return types[variable.getIndex()]; + } + + private void calculate(int initialVariable) { + var stack = new IntStack(); + stack.push(initialVariable); + while (!stack.isEmpty()) { + var variable = stack.pop(); + if (calculating[variable]) { + calculating[variable] = false; + present[variable] = true; + var inputs = phis[variable]; + ValueType type; + if (inputs != null) { + type = null; + var initialized = false; + for (var input : inputs) { + if (calculating[input]) { + continue; + } + if (initialized) { + if (!type.equals(types[input])) { + type = null; + break; + } + } else { + type = types[input]; + } + } + } else if (assignments[variable] >= 0) { + type = types[assignments[variable]]; + } else if (elementAssignments[variable] >= 0) { + type = types[elementAssignments[variable]]; + type = type instanceof ValueType.Array ? ((ValueType.Array) type).getItemType() : null; + } else { + type = null; + } + types[variable] = type; + } else { + calculating[variable] = true; + stack.push(variable); + var inputs = phis[variable]; + if (inputs != null) { + for (var input : inputs) { + if (!calculating[input] && !present[input]) { + stack.push(input); + } + } + } + var assign = assignments[variable]; + if (assign >= 0 && !present[assign]) { + stack.push(assign); + } + var elemAssign = elementAssignments[variable]; + if (elemAssign >= 0 && !present[elemAssign]) { + stack.push(elemAssign); + } + } + } + } + } + + private static class InitialTypeVisitor extends AbstractInstructionVisitor { + private ValueType[] types; + private int[] assignments; + private int[] elementAssignments; + + InitialTypeVisitor(ValueType[] types, int[] assignments, int[] elementAssignments) { + this.types = types; + this.assignments = assignments; + this.elementAssignments = elementAssignments; + } + + @Override + public void visit(ConstructArrayInstruction insn) { + types[insn.getReceiver().getIndex()] = insn.getItemType(); + super.visit(insn); + } + + @Override + public void visit(ConstructMultiArrayInstruction insn) { + var type = insn.getItemType(); + for (var i = 1; i < insn.getDimensions().size(); ++i) { + type = ValueType.arrayOf(type); + } + types[insn.getReceiver().getIndex()] = insn.getItemType(); + } + + @Override + public void visit(CastInstruction insn) { + type(insn.getReceiver(), insn.getTargetType()); + } + + @Override + public void visit(InvokeInstruction insn) { + type(insn.getReceiver(), insn.getMethod().getReturnType()); + } + + @Override + public void visit(GetFieldInstruction insn) { + type(insn.getReceiver(), insn.getFieldType()); + } + + void type(Variable target, ValueType type) { + if (target != null && type instanceof ValueType.Array) { + types[target.getIndex()] = ((ValueType.Array) type).getItemType(); + } + } + + @Override + public void visit(UnwrapArrayInstruction insn) { + assignments[insn.getReceiver().getIndex()] = insn.getArray().getIndex(); + } + + @Override + public void visit(GetElementInstruction insn) { + elementAssignments[insn.getReceiver().getIndex()] = insn.getArray().getIndex(); + } + + @Override + public void visit(AssignInstruction insn) { + assignments[insn.getReceiver().getIndex()] = insn.getAssignee().getIndex(); + } + + @Override + public void visit(NullCheckInstruction insn) { + assignments[insn.getReceiver().getIndex()] = insn.getValue().getIndex(); + } + } +} diff --git a/core/src/main/java/org/teavm/vm/StdlibDependencyListener.java b/core/src/main/java/org/teavm/vm/StdlibDependencyListener.java new file mode 100644 index 000000000..2e269b7e9 --- /dev/null +++ b/core/src/main/java/org/teavm/vm/StdlibDependencyListener.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 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.vm; + +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.MethodDependency; +import org.teavm.model.MethodReference; + +public class StdlibDependencyListener extends AbstractDependencyListener { + private static final MethodReference ARRAY_COPY_METHOD = new MethodReference(System.class, + "arraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class); + private static final MethodReference FAST_ARRAY_COPY_METHOD = new MethodReference(System.class, + "fastArraycopy", Object.class, int.class, Object.class, int.class, int.class, void.class); + private boolean reached; + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (!reached && method.getReference().equals(ARRAY_COPY_METHOD)) { + reached = true; + agent.linkMethod(FAST_ARRAY_COPY_METHOD).use(); + } + } +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 99b944793..4ffeef2c1 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -90,6 +90,7 @@ import org.teavm.model.optimization.RedundantJumpElimination; import org.teavm.model.optimization.RedundantNullCheckElimination; import org.teavm.model.optimization.RepeatedFieldReadElimination; import org.teavm.model.optimization.ScalarReplacement; +import org.teavm.model.optimization.SystemArrayCopyOptimization; import org.teavm.model.optimization.UnreachableBasicBlockElimination; import org.teavm.model.optimization.UnusedVariableElimination; import org.teavm.model.text.ListingBuilder; @@ -375,6 +376,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return !cancelled; }); target.contributeDependencies(dependencyAnalyzer); + dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener()); dependencyAnalyzer.processDependencies(); if (wasCancelled() || !diagnostics.getSevereProblems().isEmpty()) { return; @@ -782,6 +784,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository { public ClassReaderSource getClassSource() { return dependencyAnalyzer.getClassSource(); } + + @Override + public ClassHierarchy getHierarchy() { + return dependencyAnalyzer.getClassHierarchy(); + } } private List getOptimizations() { @@ -806,6 +813,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { optimizations.add(new ClassInitElimination()); optimizations.add(new UnreachableBasicBlockElimination()); optimizations.add(new UnusedVariableElimination()); + optimizations.add(new SystemArrayCopyOptimization()); return optimizations; } diff --git a/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java b/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java index 09eaff1f0..28250a2de 100644 --- a/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java +++ b/core/src/test/java/org/teavm/model/optimization/test/RepeatedFieldReadEliminationTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import org.junit.rules.TestName; import org.teavm.dependency.DependencyInfo; import org.teavm.model.AccessLevel; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; @@ -154,6 +155,11 @@ public class RepeatedFieldReadEliminationTest { public ClassReaderSource getClassSource() { return classSource; } + + @Override + public ClassHierarchy getHierarchy() { + return null; + } }; new RepeatedFieldReadElimination().optimize(context, program); diff --git a/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java b/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java index 4817fc3c7..96597f875 100644 --- a/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java +++ b/core/src/test/java/org/teavm/model/optimization/test/ScalarReplacementTest.java @@ -20,6 +20,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.teavm.dependency.DependencyInfo; +import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReaderSource; import org.teavm.model.ListingParseUtils; @@ -116,6 +117,11 @@ public class ScalarReplacementTest { public ClassReaderSource getClassSource() { return null; } + + @Override + public ClassHierarchy getHierarchy() { + return null; + } }; new ScalarReplacement().optimize(context, program);