classlib: faster implementation of System.arraycopy

This commit is contained in:
Alexey Andreev 2023-07-19 21:11:27 +02:00
parent b11ad994fd
commit 61eb666503
8 changed files with 321 additions and 2 deletions

View File

@ -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("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine();
writer.append(dest + ".data[" + destPos + "++] = " + src + ".data[" + srcPos + "++];").softNewLine(); writer.append(dest + ".data[" + destPos + "++] = " + src + ".data[" + srcPos + "++];").softNewLine();
writer.outdent().append("}").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(srcPos + " = (" + srcPos + " + " + length + ") | 0;").softNewLine();
writer.append(destPos + " = (" + destPos + " + " + length + ") | 0;").softNewLine(); writer.append(destPos + " = (" + destPos + " + " + length + ") | 0;").softNewLine();
writer.append("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine(); writer.append("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine();

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.classlib.java.lang; package org.teavm.classlib.java.lang;
import java.lang.reflect.Array;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Properties; import java.util.Properties;
import org.teavm.backend.c.intrinsic.RuntimeInclude; import org.teavm.backend.c.intrinsic.RuntimeInclude;
@ -115,10 +116,18 @@ public final class TSystem extends TObject {
doArrayCopy(src, srcPos, dest, destPos, length); 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) @GeneratedBy(SystemNativeGenerator.class)
@DelegateTo("doArrayCopyLowLevel") @DelegateTo("doArrayCopyLowLevel")
@NoSideEffects @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 @Unmanaged
static void doArrayCopyLowLevel(RuntimeArray src, int srcPos, RuntimeArray dest, int destPos, int length) { static void doArrayCopyLowLevel(RuntimeArray src, int srcPos, RuntimeArray dest, int destPos, int length) {

View File

@ -16,6 +16,7 @@
package org.teavm.model.optimization; package org.teavm.model.optimization;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
@ -25,4 +26,6 @@ public interface MethodOptimizationContext {
DependencyInfo getDependencyInfo(); DependencyInfo getDependencyInfo();
ClassReaderSource getClassSource(); ClassReaderSource getClassSource();
ClassHierarchy getHierarchy();
} }

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -90,6 +90,7 @@ import org.teavm.model.optimization.RedundantJumpElimination;
import org.teavm.model.optimization.RedundantNullCheckElimination; import org.teavm.model.optimization.RedundantNullCheckElimination;
import org.teavm.model.optimization.RepeatedFieldReadElimination; import org.teavm.model.optimization.RepeatedFieldReadElimination;
import org.teavm.model.optimization.ScalarReplacement; import org.teavm.model.optimization.ScalarReplacement;
import org.teavm.model.optimization.SystemArrayCopyOptimization;
import org.teavm.model.optimization.UnreachableBasicBlockElimination; import org.teavm.model.optimization.UnreachableBasicBlockElimination;
import org.teavm.model.optimization.UnusedVariableElimination; import org.teavm.model.optimization.UnusedVariableElimination;
import org.teavm.model.text.ListingBuilder; import org.teavm.model.text.ListingBuilder;
@ -375,6 +376,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return !cancelled; return !cancelled;
}); });
target.contributeDependencies(dependencyAnalyzer); target.contributeDependencies(dependencyAnalyzer);
dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener());
dependencyAnalyzer.processDependencies(); dependencyAnalyzer.processDependencies();
if (wasCancelled() || !diagnostics.getSevereProblems().isEmpty()) { if (wasCancelled() || !diagnostics.getSevereProblems().isEmpty()) {
return; return;
@ -782,6 +784,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
public ClassReaderSource getClassSource() { public ClassReaderSource getClassSource() {
return dependencyAnalyzer.getClassSource(); return dependencyAnalyzer.getClassSource();
} }
@Override
public ClassHierarchy getHierarchy() {
return dependencyAnalyzer.getClassHierarchy();
}
} }
private List<MethodOptimization> getOptimizations() { private List<MethodOptimization> getOptimizations() {
@ -806,6 +813,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
optimizations.add(new ClassInitElimination()); optimizations.add(new ClassInitElimination());
optimizations.add(new UnreachableBasicBlockElimination()); optimizations.add(new UnreachableBasicBlockElimination());
optimizations.add(new UnusedVariableElimination()); optimizations.add(new UnusedVariableElimination());
optimizations.add(new SystemArrayCopyOptimization());
return optimizations; return optimizations;
} }

View File

@ -21,6 +21,7 @@ import org.junit.Test;
import org.junit.rules.TestName; import org.junit.rules.TestName;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.model.AccessLevel; import org.teavm.model.AccessLevel;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
@ -154,6 +155,11 @@ public class RepeatedFieldReadEliminationTest {
public ClassReaderSource getClassSource() { public ClassReaderSource getClassSource() {
return classSource; return classSource;
} }
@Override
public ClassHierarchy getHierarchy() {
return null;
}
}; };
new RepeatedFieldReadElimination().optimize(context, program); new RepeatedFieldReadElimination().optimize(context, program);

View File

@ -20,6 +20,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestName; import org.junit.rules.TestName;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListingParseUtils; import org.teavm.model.ListingParseUtils;
@ -116,6 +117,11 @@ public class ScalarReplacementTest {
public ClassReaderSource getClassSource() { public ClassReaderSource getClassSource() {
return null; return null;
} }
@Override
public ClassHierarchy getHierarchy() {
return null;
}
}; };
new ScalarReplacement().optimize(context, program); new ScalarReplacement().optimize(context, program);