mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
Initial optimization of dependency checker
This commit is contained in:
parent
1380e7dbf4
commit
2745f1c7f5
|
@ -36,14 +36,20 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
private ObjectIntMap<MethodReference> methodNodes = new ObjectIntOpenHashMap<>();
|
private ObjectIntMap<MethodReference> methodNodes = new ObjectIntOpenHashMap<>();
|
||||||
private ObjectIntMap<FieldReference> fieldNodes = new ObjectIntOpenHashMap<>();
|
private ObjectIntMap<FieldReference> fieldNodes = new ObjectIntOpenHashMap<>();
|
||||||
private int[] arrayNodes;
|
private int[] arrayNodes;
|
||||||
|
private int returnIndex = -1;
|
||||||
|
private int exceptionIndex;
|
||||||
|
|
||||||
public void important(int node) {
|
public void important(int node) {
|
||||||
importantNodes.add(node);
|
importantNodes.add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] buildMapping(ProgramReader program, int paramCount) {
|
public int[] buildMapping(ProgramReader program, int paramCount, boolean needsReturn) {
|
||||||
lastIndex = program.variableCount();
|
lastIndex = program.variableCount();
|
||||||
arrayNodes = new int[lastIndex];
|
arrayNodes = new int[lastIndex];
|
||||||
|
if (needsReturn) {
|
||||||
|
returnIndex = lastIndex++;
|
||||||
|
}
|
||||||
|
exceptionIndex = lastIndex++;
|
||||||
Arrays.fill(arrayNodes, -1);
|
Arrays.fill(arrayNodes, -1);
|
||||||
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
||||||
BasicBlockReader block = program.basicBlockAt(i);
|
BasicBlockReader block = program.basicBlockAt(i);
|
||||||
|
@ -52,6 +58,11 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
|
builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
|
||||||
|
if (tryCatch.getExceptionVariable() != null) {
|
||||||
|
important(tryCatch.getExceptionVariable().getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
block.readAllInstructions(this);
|
block.readAllInstructions(this);
|
||||||
}
|
}
|
||||||
Graph graph = builder.build();
|
Graph graph = builder.build();
|
||||||
|
@ -76,6 +87,17 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
importantNodes.add(newCls);
|
importantNodes.add(newCls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (int succ : graph.outgoingEdges(i)) {
|
||||||
|
boolean succImportant = importantNodes.contains(classes.find(succ));
|
||||||
|
boolean nodeImportant = importantNodes.contains(classes.find(i));
|
||||||
|
if (succImportant && nodeImportant) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int newCls = classes.union(succ, i);
|
||||||
|
if (nodeImportant || succImportant) {
|
||||||
|
importantNodes.add(newCls);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, startNodes.getAll());
|
int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph, startNodes.getAll());
|
||||||
|
@ -88,7 +110,25 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
last = last < 0 ? node : classes.union(node, last);
|
last = last < 0 ? node : classes.union(node, last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return classes.pack(program.variableCount());
|
|
||||||
|
int[] classMap = new int[classes.size()];
|
||||||
|
Arrays.fill(classMap, -1);
|
||||||
|
int[] result = new int[program.variableCount()];
|
||||||
|
int classCount = 0;
|
||||||
|
for (int i = 0; i < program.variableCount(); ++i) {
|
||||||
|
int cls = classes.find(i);
|
||||||
|
if (!importantNodes.contains(cls)) {
|
||||||
|
result[i] = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int packedCls = classMap[cls];
|
||||||
|
if (packedCls < 0) {
|
||||||
|
packedCls = classCount++;
|
||||||
|
classMap[cls] = packedCls;
|
||||||
|
}
|
||||||
|
result[i] = packedCls;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -178,14 +218,16 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit(VariableReader valueToReturn) {
|
public void exit(VariableReader valueToReturn) {
|
||||||
if (valueToReturn != null) {
|
if (valueToReturn != null && returnIndex >= 0) {
|
||||||
important(valueToReturn.getIndex());
|
important(returnIndex);
|
||||||
|
builder.addEdge(valueToReturn.getIndex(), returnIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void raise(VariableReader exception) {
|
public void raise(VariableReader exception) {
|
||||||
important(exception.getIndex());
|
builder.addEdge(exception.getIndex(), exceptionIndex);
|
||||||
|
important(exceptionIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -212,13 +254,18 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType) {
|
public void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType) {
|
||||||
|
if (fieldType instanceof ValueType.Primitive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
int fieldNode = getFieldNode(field);
|
int fieldNode = getFieldNode(field);
|
||||||
builder.addEdge(fieldNode, receiver.getIndex());
|
builder.addEdge(fieldNode, receiver.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putField(VariableReader instance, FieldReference field, VariableReader value) {
|
public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) {
|
||||||
|
if (fieldType instanceof ValueType.Primitive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
int fieldNode = getFieldNode(field);
|
int fieldNode = getFieldNode(field);
|
||||||
builder.addEdge(value.getIndex(), fieldNode);
|
builder.addEdge(value.getIndex(), fieldNode);
|
||||||
}
|
}
|
||||||
|
@ -274,9 +321,20 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
|
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
|
||||||
List<? extends VariableReader> arguments, InvocationType type) {
|
List<? extends VariableReader> arguments, InvocationType type) {
|
||||||
if (receiver != null) {
|
if (receiver != null) {
|
||||||
|
if (!(method.getReturnType() instanceof ValueType.Primitive)) {
|
||||||
builder.addEdge(getMethodNode(method), receiver.getIndex());
|
builder.addEdge(getMethodNode(method), receiver.getIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ValueType[] paramTypes = method.getParameterTypes();
|
||||||
|
for (int i = 0; i < paramTypes.length; ++i) {
|
||||||
|
if (!(paramTypes[i] instanceof ValueType.Primitive)) {
|
||||||
|
important(arguments.get(i).getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (instance != null) {
|
||||||
|
important(instance.getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void isInstance(VariableReader receiver, VariableReader value, ValueType type) {
|
public void isInstance(VariableReader receiver, VariableReader value, ValueType type) {
|
||||||
|
@ -293,9 +351,11 @@ public class DataFlowGraphBuilder implements InstructionReader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void monitorEnter(VariableReader objectRef) {
|
public void monitorEnter(VariableReader objectRef) {
|
||||||
|
important(objectRef.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void monitorExit(VariableReader objectRef) {
|
public void monitorExit(VariableReader objectRef) {
|
||||||
|
important(objectRef.getIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,8 @@ public class DependencyChecker implements DependencyInfo {
|
||||||
private Diagnostics diagnostics;
|
private Diagnostics diagnostics;
|
||||||
DefaultCallGraph callGraph = new DefaultCallGraph();
|
DefaultCallGraph callGraph = new DefaultCallGraph();
|
||||||
private DependencyAgent agent;
|
private DependencyAgent agent;
|
||||||
|
List<DependencyNode> nodes = new ArrayList<>();
|
||||||
|
List<BitSet> typeBitSets = new ArrayList<>();
|
||||||
|
|
||||||
public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services,
|
public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services,
|
||||||
Diagnostics diagnostics) {
|
Diagnostics diagnostics) {
|
||||||
|
@ -128,13 +130,16 @@ public class DependencyChecker implements DependencyInfo {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
type = new DependencyType(this, name, types.size());
|
type = new DependencyType(this, name, types.size());
|
||||||
types.add(type);
|
types.add(type);
|
||||||
|
typeBitSets.add(new BitSet(nodes.size()));
|
||||||
typeMap.put(name, type);
|
typeMap.put(name, type);
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DependencyNode createNode() {
|
public DependencyNode createNode() {
|
||||||
return new DependencyNode(this);
|
DependencyNode node = new DependencyNode(this, nodes.size());
|
||||||
|
nodes.add(node);
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -336,11 +341,9 @@ public class DependencyChecker implements DependencyInfo {
|
||||||
private MethodDependency createMethodDep(MethodReference methodRef, MethodReader method) {
|
private MethodDependency createMethodDep(MethodReference methodRef, MethodReader method) {
|
||||||
ValueType[] arguments = methodRef.getParameterTypes();
|
ValueType[] arguments = methodRef.getParameterTypes();
|
||||||
int paramCount = arguments.length + 1;
|
int paramCount = arguments.length + 1;
|
||||||
int varCount = Math.max(paramCount, method != null && method.getProgram() != null ?
|
DependencyNode[] parameterNodes = new DependencyNode[arguments.length + 1];
|
||||||
method.getProgram().variableCount() : 0);
|
for (int i = 0; i < parameterNodes.length; ++i) {
|
||||||
DependencyNode[] parameterNodes = new DependencyNode[varCount];
|
parameterNodes[i] = createNode();
|
||||||
for (int i = 0; i < varCount; ++i) {
|
|
||||||
parameterNodes[i] = new DependencyNode(this);
|
|
||||||
if (shouldLog) {
|
if (shouldLog) {
|
||||||
parameterNodes[i].setTag(methodRef + ":" + i);
|
parameterNodes[i].setTag(methodRef + ":" + i);
|
||||||
}
|
}
|
||||||
|
@ -349,7 +352,7 @@ public class DependencyChecker implements DependencyInfo {
|
||||||
if (methodRef.getDescriptor().getResultType() == ValueType.VOID) {
|
if (methodRef.getDescriptor().getResultType() == ValueType.VOID) {
|
||||||
resultNode = null;
|
resultNode = null;
|
||||||
} else {
|
} else {
|
||||||
resultNode = new DependencyNode(this);
|
resultNode = createNode();
|
||||||
if (shouldLog) {
|
if (shouldLog) {
|
||||||
resultNode.setTag(methodRef + ":RESULT");
|
resultNode.setTag(methodRef + ":RESULT");
|
||||||
}
|
}
|
||||||
|
@ -431,7 +434,7 @@ public class DependencyChecker implements DependencyInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
private FieldDependency createFieldNode(final FieldReference fieldRef, FieldReader field) {
|
private FieldDependency createFieldNode(final FieldReference fieldRef, FieldReader field) {
|
||||||
DependencyNode node = new DependencyNode(this);
|
DependencyNode node = createNode();
|
||||||
if (shouldLog) {
|
if (shouldLog) {
|
||||||
node.setTag(fieldRef.getClassName() + "#" + fieldRef.getFieldName());
|
node.setTag(fieldRef.getClassName() + "#" + fieldRef.getFieldName());
|
||||||
}
|
}
|
||||||
|
@ -501,6 +504,7 @@ public class DependencyChecker implements DependencyInfo {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T getService(Class<T> type) {
|
public <T> T getService(Class<T> type) {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.dependency;
|
package org.teavm.dependency;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -48,20 +49,29 @@ class DependencyGraphBuilder {
|
||||||
}
|
}
|
||||||
program = method.getProgram();
|
program = method.getProgram();
|
||||||
if (DependencyChecker.shouldLog) {
|
if (DependencyChecker.shouldLog) {
|
||||||
System.out.println("Method achieved: " + method.getReference());
|
System.out.println("Method reached: " + method.getReference());
|
||||||
System.out.println(new ListingBuilder().buildListing(program, " "));
|
System.out.println(new ListingBuilder().buildListing(program, " "));
|
||||||
}
|
}
|
||||||
resultNode = dep.getResult();
|
resultNode = dep.getResult();
|
||||||
|
|
||||||
DependencyNode[] origNodes = dep.getVariables();
|
|
||||||
DataFlowGraphBuilder dfgBuilder = new DataFlowGraphBuilder();
|
DataFlowGraphBuilder dfgBuilder = new DataFlowGraphBuilder();
|
||||||
for (int i = 0; i < dep.getParameterCount(); ++i) {
|
for (int i = 0; i < dep.getParameterCount(); ++i) {
|
||||||
dfgBuilder.important(i);
|
dfgBuilder.important(i);
|
||||||
}
|
}
|
||||||
int[] nodeMapping = dfgBuilder.buildMapping(program, dep.getParameterCount());
|
int[] nodeMapping = dfgBuilder.buildMapping(program, dep.getParameterCount(),
|
||||||
nodes = new DependencyNode[origNodes.length];
|
!(method.getResultType() instanceof ValueType.Primitive) && method.getResultType() != ValueType.VOID);
|
||||||
|
int nodeClassCount = 0;
|
||||||
|
for (int i = 0; i < nodeMapping.length; ++i) {
|
||||||
|
nodeClassCount = Math.max(nodeClassCount, nodeMapping[i] + 1);
|
||||||
|
}
|
||||||
|
DependencyNode[] nodeClasses = Arrays.copyOf(dep.getVariables(), nodeClassCount);
|
||||||
|
for (int i = dep.getVariableCount(); i < nodeClasses.length; ++i) {
|
||||||
|
nodeClasses[i] = dependencyChecker.createNode();
|
||||||
|
}
|
||||||
|
nodes = new DependencyNode[dep.getMethod().getProgram().variableCount()];
|
||||||
for (int i = 0; i < nodes.length; ++i) {
|
for (int i = 0; i < nodes.length; ++i) {
|
||||||
nodes[i] = origNodes[nodeMapping[i]];
|
int mappedNode = nodeMapping[i];
|
||||||
|
nodes[i] = mappedNode >= 0 ? nodeClasses[mappedNode] : null;
|
||||||
}
|
}
|
||||||
dep.setVariables(nodes);
|
dep.setVariables(nodes);
|
||||||
|
|
||||||
|
@ -71,9 +81,13 @@ class DependencyGraphBuilder {
|
||||||
block.readAllInstructions(reader);
|
block.readAllInstructions(reader);
|
||||||
for (PhiReader phi : block.readPhis()) {
|
for (PhiReader phi : block.readPhis()) {
|
||||||
for (IncomingReader incoming : phi.readIncomings()) {
|
for (IncomingReader incoming : phi.readIncomings()) {
|
||||||
|
DependencyNode incomingNode = nodes[incoming.getValue().getIndex()];
|
||||||
|
DependencyNode receiverNode = nodes[phi.getReceiver().getIndex()];
|
||||||
|
if (incomingNode != null || receiverNode != null) {
|
||||||
nodes[incoming.getValue().getIndex()].connect(nodes[phi.getReceiver().getIndex()]);
|
nodes[incoming.getValue().getIndex()].connect(nodes[phi.getReceiver().getIndex()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
|
for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
|
||||||
if (tryCatch.getExceptionType() != null) {
|
if (tryCatch.getExceptionType() != null) {
|
||||||
dependencyChecker.linkClass(tryCatch.getExceptionType(), new CallLocation(caller.getMethod()));
|
dependencyChecker.linkClass(tryCatch.getExceptionType(), new CallLocation(caller.getMethod()));
|
||||||
|
@ -169,8 +183,10 @@ class DependencyGraphBuilder {
|
||||||
methodDep.use();
|
methodDep.use();
|
||||||
DependencyNode[] targetParams = methodDep.getVariables();
|
DependencyNode[] targetParams = methodDep.getVariables();
|
||||||
for (int i = 0; i < parameters.length; ++i) {
|
for (int i = 0; i < parameters.length; ++i) {
|
||||||
|
if (parameters[i] != null) {
|
||||||
parameters[i].connect(targetParams[i]);
|
parameters[i].connect(targetParams[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (result != null && methodDep.getResult() != null) {
|
if (result != null && methodDep.getResult() != null) {
|
||||||
methodDep.getResult().connect(result);
|
methodDep.getResult().connect(result);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +227,10 @@ class DependencyGraphBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void classConstant(VariableReader receiver, ValueType cst) {
|
public void classConstant(VariableReader receiver, ValueType cst) {
|
||||||
nodes[receiver.getIndex()].propagate(dependencyChecker.getType("java.lang.Class"));
|
DependencyNode node = nodes[receiver.getIndex()];
|
||||||
|
if (node != null) {
|
||||||
|
node.propagate(dependencyChecker.getType("java.lang.Class"));
|
||||||
|
}
|
||||||
while (cst instanceof ValueType.Array) {
|
while (cst instanceof ValueType.Array) {
|
||||||
cst = ((ValueType.Array)cst).getItemType();
|
cst = ((ValueType.Array)cst).getItemType();
|
||||||
}
|
}
|
||||||
|
@ -243,7 +262,10 @@ class DependencyGraphBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stringConstant(VariableReader receiver, String cst) {
|
public void stringConstant(VariableReader receiver, String cst) {
|
||||||
nodes[receiver.getIndex()].propagate(dependencyChecker.getType("java.lang.String"));
|
DependencyNode node = nodes[receiver.getIndex()];
|
||||||
|
if (node != null) {
|
||||||
|
node.propagate(dependencyChecker.getType("java.lang.String"));
|
||||||
|
}
|
||||||
MethodDependency method = dependencyChecker.linkMethod(new MethodReference(String.class,
|
MethodDependency method = dependencyChecker.linkMethod(new MethodReference(String.class,
|
||||||
"<init>", char[].class, void.class), new CallLocation(caller.getMethod(), currentLocation));
|
"<init>", char[].class, void.class), new CallLocation(caller.getMethod(), currentLocation));
|
||||||
method.use();
|
method.use();
|
||||||
|
@ -262,8 +284,10 @@ class DependencyGraphBuilder {
|
||||||
public void assign(VariableReader receiver, VariableReader assignee) {
|
public void assign(VariableReader receiver, VariableReader assignee) {
|
||||||
DependencyNode valueNode = nodes[assignee.getIndex()];
|
DependencyNode valueNode = nodes[assignee.getIndex()];
|
||||||
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
||||||
|
if (valueNode != null && receiverNode != null) {
|
||||||
valueNode.connect(receiverNode);
|
valueNode.connect(receiverNode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
|
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
|
||||||
|
@ -319,7 +343,10 @@ class DependencyGraphBuilder {
|
||||||
@Override
|
@Override
|
||||||
public void exit(VariableReader valueToReturn) {
|
public void exit(VariableReader valueToReturn) {
|
||||||
if (valueToReturn != null) {
|
if (valueToReturn != null) {
|
||||||
nodes[valueToReturn.getIndex()].connect(resultNode);
|
DependencyNode node = nodes[valueToReturn.getIndex()];
|
||||||
|
if (node != null) {
|
||||||
|
node.connect(resultNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +357,10 @@ class DependencyGraphBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) {
|
public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) {
|
||||||
nodes[receiver.getIndex()].propagate(dependencyChecker.getType("[" + itemType));
|
DependencyNode node = nodes[receiver.getIndex()];
|
||||||
|
if (node != null) {
|
||||||
|
node.propagate(dependencyChecker.getType("[" + itemType));
|
||||||
|
}
|
||||||
String className = extractClassName(itemType);
|
String className = extractClassName(itemType);
|
||||||
if (className != null) {
|
if (className != null) {
|
||||||
dependencyChecker.linkClass(className, new CallLocation(caller.getMethod(), currentLocation));
|
dependencyChecker.linkClass(className, new CallLocation(caller.getMethod(), currentLocation));
|
||||||
|
@ -361,6 +391,9 @@ class DependencyGraphBuilder {
|
||||||
sb.append(itemTypeStr);
|
sb.append(itemTypeStr);
|
||||||
DependencyNode node = nodes[receiver.getIndex()];
|
DependencyNode node = nodes[receiver.getIndex()];
|
||||||
for (int i = 0; i < dimensions.size(); ++i) {
|
for (int i = 0; i < dimensions.size(); ++i) {
|
||||||
|
if (node == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
node.propagate(dependencyChecker.getType(sb.substring(i, sb.length())));
|
node.propagate(dependencyChecker.getType(sb.substring(i, sb.length())));
|
||||||
node = node.getArrayItem();
|
node = node.getArrayItem();
|
||||||
}
|
}
|
||||||
|
@ -373,25 +406,37 @@ class DependencyGraphBuilder {
|
||||||
@Override
|
@Override
|
||||||
public void create(VariableReader receiver, String type) {
|
public void create(VariableReader receiver, String type) {
|
||||||
dependencyChecker.linkClass(type, new CallLocation(caller.getMethod(), currentLocation));
|
dependencyChecker.linkClass(type, new CallLocation(caller.getMethod(), currentLocation));
|
||||||
nodes[receiver.getIndex()].propagate(dependencyChecker.getType(type));
|
DependencyNode node = nodes[receiver.getIndex()];
|
||||||
|
if (node != null) {
|
||||||
|
node.propagate(dependencyChecker.getType(type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getField(VariableReader receiver, VariableReader instance, FieldReference field,
|
public void getField(VariableReader receiver, VariableReader instance, FieldReference field,
|
||||||
ValueType fieldType) {
|
ValueType fieldType) {
|
||||||
|
if (!(fieldType instanceof ValueType.Primitive)) {
|
||||||
FieldDependency fieldDep = dependencyChecker.linkField(field,
|
FieldDependency fieldDep = dependencyChecker.linkField(field,
|
||||||
new CallLocation(caller.getMethod(), currentLocation));
|
new CallLocation(caller.getMethod(), currentLocation));
|
||||||
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
||||||
|
if (receiverNode != null) {
|
||||||
fieldDep.getValue().connect(receiverNode);
|
fieldDep.getValue().connect(receiverNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
initClass(field.getClassName());
|
initClass(field.getClassName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putField(VariableReader instance, FieldReference field, VariableReader value) {
|
public void putField(VariableReader instance, FieldReference field, VariableReader value,
|
||||||
|
ValueType fieldType) {
|
||||||
|
if (!(fieldType instanceof ValueType.Primitive)) {
|
||||||
FieldDependency fieldDep = dependencyChecker.linkField(field,
|
FieldDependency fieldDep = dependencyChecker.linkField(field,
|
||||||
new CallLocation(caller.getMethod(), currentLocation));
|
new CallLocation(caller.getMethod(), currentLocation));
|
||||||
DependencyNode valueNode = nodes[value.getIndex()];
|
DependencyNode valueNode = nodes[value.getIndex()];
|
||||||
|
if (valueNode != null) {
|
||||||
valueNode.connect(fieldDep.getValue());
|
valueNode.connect(fieldDep.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
initClass(field.getClassName());
|
initClass(field.getClassName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,6 +448,7 @@ class DependencyGraphBuilder {
|
||||||
public void cloneArray(VariableReader receiver, VariableReader array) {
|
public void cloneArray(VariableReader receiver, VariableReader array) {
|
||||||
DependencyNode arrayNode = nodes[array.getIndex()];
|
DependencyNode arrayNode = nodes[array.getIndex()];
|
||||||
final DependencyNode receiverNode = nodes[receiver.getIndex()];
|
final DependencyNode receiverNode = nodes[receiver.getIndex()];
|
||||||
|
if (arrayNode != null && receiverNode != null) {
|
||||||
arrayNode.addConsumer(new DependencyConsumer() {
|
arrayNode.addConsumer(new DependencyConsumer() {
|
||||||
@Override public void consume(DependencyType type) {
|
@Override public void consume(DependencyType type) {
|
||||||
receiverNode.propagate(type);
|
receiverNode.propagate(type);
|
||||||
|
@ -410,27 +456,34 @@ class DependencyGraphBuilder {
|
||||||
});
|
});
|
||||||
arrayNode.getArrayItem().connect(receiverNode.getArrayItem());
|
arrayNode.getArrayItem().connect(receiverNode.getArrayItem());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) {
|
public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) {
|
||||||
DependencyNode arrayNode = nodes[array.getIndex()];
|
DependencyNode arrayNode = nodes[array.getIndex()];
|
||||||
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
||||||
|
if (arrayNode != null && receiverNode != null) {
|
||||||
arrayNode.connect(receiverNode);
|
arrayNode.connect(receiverNode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getElement(VariableReader receiver, VariableReader array, VariableReader index) {
|
public void getElement(VariableReader receiver, VariableReader array, VariableReader index) {
|
||||||
DependencyNode arrayNode = nodes[array.getIndex()];
|
DependencyNode arrayNode = nodes[array.getIndex()];
|
||||||
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
||||||
|
if (arrayNode != null && receiverNode != null) {
|
||||||
arrayNode.getArrayItem().connect(receiverNode);
|
arrayNode.getArrayItem().connect(receiverNode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putElement(VariableReader array, VariableReader index, VariableReader value) {
|
public void putElement(VariableReader array, VariableReader index, VariableReader value) {
|
||||||
DependencyNode valueNode = nodes[value.getIndex()];
|
DependencyNode valueNode = nodes[value.getIndex()];
|
||||||
DependencyNode arrayNode = nodes[array.getIndex()];
|
DependencyNode arrayNode = nodes[array.getIndex()];
|
||||||
|
if (valueNode != null && arrayNode != null) {
|
||||||
valueNode.connect(arrayNode.getArrayItem());
|
valueNode.connect(arrayNode.getArrayItem());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
|
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
|
||||||
|
@ -460,13 +513,20 @@ class DependencyGraphBuilder {
|
||||||
methodDep.use();
|
methodDep.use();
|
||||||
DependencyNode[] targetParams = methodDep.getVariables();
|
DependencyNode[] targetParams = methodDep.getVariables();
|
||||||
for (int i = 0; i < arguments.size(); ++i) {
|
for (int i = 0; i < arguments.size(); ++i) {
|
||||||
nodes[arguments.get(i).getIndex()].connect(targetParams[i + 1]);
|
DependencyNode value = nodes[arguments.get(i).getIndex()];
|
||||||
|
DependencyNode param = targetParams[i + 1];
|
||||||
|
if (value != null && param != null) {
|
||||||
|
value.connect(param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
nodes[instance.getIndex()].connect(targetParams[0]);
|
nodes[instance.getIndex()].connect(targetParams[0]);
|
||||||
}
|
}
|
||||||
if (methodDep.getResult() != null && receiver != null) {
|
if (methodDep.getResult() != null && receiver != null) {
|
||||||
methodDep.getResult().connect(nodes[receiver.getIndex()]);
|
DependencyNode receiverNode = nodes[receiver.getIndex()];
|
||||||
|
if (methodDep.getResult() != null && receiverNode != null) {
|
||||||
|
methodDep.getResult().connect(receiverNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
methodDep.getThrown().addConsumer(currentExceptionConsumer);
|
methodDep.getThrown().addConsumer(currentExceptionConsumer);
|
||||||
initClass(method.getClassName());
|
initClass(method.getClassName());
|
||||||
|
|
|
@ -23,19 +23,21 @@ import java.util.*;
|
||||||
*/
|
*/
|
||||||
public class DependencyNode implements ValueDependencyInfo {
|
public class DependencyNode implements ValueDependencyInfo {
|
||||||
private DependencyChecker dependencyChecker;
|
private DependencyChecker dependencyChecker;
|
||||||
private Set<DependencyConsumer> followers = new HashSet<>();
|
private List<DependencyConsumer> followers;
|
||||||
private BitSet types;
|
private BitSet types;
|
||||||
private Map<DependencyNode, DependencyNodeToNodeTransition> transitions = new HashMap<>();
|
private List<DependencyNodeToNodeTransition> transitions;
|
||||||
private volatile String tag;
|
private volatile String tag;
|
||||||
private DependencyNode arrayItemNode;
|
private DependencyNode arrayItemNode;
|
||||||
private int degree;
|
private int degree;
|
||||||
|
int index;
|
||||||
|
|
||||||
DependencyNode(DependencyChecker dependencyChecker) {
|
DependencyNode(DependencyChecker dependencyChecker, int index) {
|
||||||
this(dependencyChecker, 0);
|
this(dependencyChecker, index, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
DependencyNode(DependencyChecker dependencyChecker, int degree) {
|
DependencyNode(DependencyChecker dependencyChecker, int index, int degree) {
|
||||||
this.dependencyChecker = dependencyChecker;
|
this.dependencyChecker = dependencyChecker;
|
||||||
|
this.index = index;
|
||||||
this.degree = degree;
|
this.degree = degree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,11 +56,13 @@ public class DependencyNode implements ValueDependencyInfo {
|
||||||
if (DependencyChecker.shouldLog) {
|
if (DependencyChecker.shouldLog) {
|
||||||
System.out.println(tag + " -> " + type.getName());
|
System.out.println(tag + " -> " + type.getName());
|
||||||
}
|
}
|
||||||
|
if (followers != null) {
|
||||||
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
|
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
|
||||||
dependencyChecker.schedulePropagation(consumer, type);
|
dependencyChecker.schedulePropagation(consumer, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void propagate(DependencyType[] agentTypes) {
|
public void propagate(DependencyType[] agentTypes) {
|
||||||
DependencyType[] types = new DependencyType[agentTypes.length];
|
DependencyType[] types = new DependencyType[agentTypes.length];
|
||||||
|
@ -81,13 +85,22 @@ public class DependencyNode implements ValueDependencyInfo {
|
||||||
System.out.println(tag + " -> " + types[i].getName());
|
System.out.println(tag + " -> " + types[i].getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (followers != null) {
|
||||||
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
|
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
|
||||||
dependencyChecker.schedulePropagation(consumer, Arrays.copyOf(types, j));
|
dependencyChecker.schedulePropagation(consumer, Arrays.copyOf(types, j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addConsumer(DependencyConsumer consumer) {
|
public void addConsumer(DependencyConsumer consumer) {
|
||||||
if (followers.add(consumer) && this.types != null) {
|
if (followers == null) {
|
||||||
|
followers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
if (followers.contains(consumer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
followers.add(consumer);
|
||||||
|
if (this.types != null) {
|
||||||
List<DependencyType> types = new ArrayList<>();
|
List<DependencyType> types = new ArrayList<>();
|
||||||
for (int index = this.types.nextSetBit(0); index >= 0; index = this.types.nextSetBit(index + 1)) {
|
for (int index = this.types.nextSetBit(0); index >= 0; index = this.types.nextSetBit(index + 1)) {
|
||||||
types.add(dependencyChecker.types.get(index));
|
types.add(dependencyChecker.types.get(index));
|
||||||
|
@ -100,15 +113,27 @@ public class DependencyNode implements ValueDependencyInfo {
|
||||||
if (this == node) {
|
if (this == node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (node == null) {
|
||||||
|
throw new IllegalArgumentException("Node must not be null");
|
||||||
|
}
|
||||||
|
if (transitions != null) {
|
||||||
|
for (DependencyNodeToNodeTransition transition : transitions) {
|
||||||
|
if (transition.destination == node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node, filter);
|
DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node, filter);
|
||||||
if (!transitions.containsKey(node)) {
|
if (transitions == null) {
|
||||||
transitions.put(node, transition);
|
transitions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions.add(transition);
|
||||||
if (DependencyChecker.shouldLog) {
|
if (DependencyChecker.shouldLog) {
|
||||||
System.out.println("Connecting " + tag + " to " + node.tag);
|
System.out.println("Connecting " + tag + " to " + node.tag);
|
||||||
}
|
}
|
||||||
addConsumer(transition);
|
addConsumer(transition);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void connect(DependencyNode node) {
|
public void connect(DependencyNode node) {
|
||||||
connect(node, null);
|
connect(node, null);
|
||||||
|
@ -117,7 +142,8 @@ public class DependencyNode implements ValueDependencyInfo {
|
||||||
@Override
|
@Override
|
||||||
public DependencyNode getArrayItem() {
|
public DependencyNode getArrayItem() {
|
||||||
if (arrayItemNode == null) {
|
if (arrayItemNode == null) {
|
||||||
arrayItemNode = new DependencyNode(dependencyChecker, degree + 1);
|
arrayItemNode = new DependencyNode(dependencyChecker, dependencyChecker.nodes.size(), degree + 1);
|
||||||
|
dependencyChecker.nodes.add(arrayItemNode);
|
||||||
if (DependencyChecker.shouldLog) {
|
if (DependencyChecker.shouldLog) {
|
||||||
arrayItemNode.tag = tag + "[";
|
arrayItemNode.tag = tag + "[";
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ package org.teavm.dependency;
|
||||||
* @author Alexey Andreev
|
* @author Alexey Andreev
|
||||||
*/
|
*/
|
||||||
class DependencyNodeToNodeTransition implements DependencyConsumer {
|
class DependencyNodeToNodeTransition implements DependencyConsumer {
|
||||||
private DependencyNode source;
|
DependencyNode source;
|
||||||
private DependencyNode destination;
|
DependencyNode destination;
|
||||||
private DependencyTypeFilter filter;
|
private DependencyTypeFilter filter;
|
||||||
|
|
||||||
public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination,
|
public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination,
|
||||||
|
|
|
@ -153,7 +153,7 @@ class InstructionReadVisitor implements InstructionVisitor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(PutFieldInstruction insn) {
|
public void visit(PutFieldInstruction insn) {
|
||||||
reader.putField(insn.getInstance(), insn.getField(), insn.getValue());
|
reader.putField(insn.getInstance(), insn.getField(), insn.getValue(), insn.getFieldType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -99,6 +99,10 @@ public class MethodReference {
|
||||||
return Arrays.copyOf(signature, signature.length);
|
return Arrays.copyOf(signature, signature.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueType getReturnType() {
|
||||||
|
return signature[signature.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ public interface InstructionReader {
|
||||||
|
|
||||||
void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType);
|
void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType);
|
||||||
|
|
||||||
void putField(VariableReader instance, FieldReference field, VariableReader value);
|
void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType);
|
||||||
|
|
||||||
void arrayLength(VariableReader receiver, VariableReader array);
|
void arrayLength(VariableReader receiver, VariableReader array);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ package org.teavm.model.instructions;
|
||||||
|
|
||||||
import org.teavm.model.FieldReference;
|
import org.teavm.model.FieldReference;
|
||||||
import org.teavm.model.Instruction;
|
import org.teavm.model.Instruction;
|
||||||
|
import org.teavm.model.ValueType;
|
||||||
import org.teavm.model.Variable;
|
import org.teavm.model.Variable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,6 +28,7 @@ public class PutFieldInstruction extends Instruction {
|
||||||
private Variable instance;
|
private Variable instance;
|
||||||
private FieldReference field;
|
private FieldReference field;
|
||||||
private Variable value;
|
private Variable value;
|
||||||
|
private ValueType fieldType;
|
||||||
|
|
||||||
public Variable getInstance() {
|
public Variable getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -52,6 +54,14 @@ public class PutFieldInstruction extends Instruction {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueType getFieldType() {
|
||||||
|
return fieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFieldType(ValueType fieldType) {
|
||||||
|
this.fieldType = fieldType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void acceptVisitor(InstructionVisitor visitor) {
|
public void acceptVisitor(InstructionVisitor visitor) {
|
||||||
visitor.visit(this);
|
visitor.visit(this);
|
||||||
|
|
|
@ -330,7 +330,8 @@ public class AsyncMethodFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putField(VariableReader instance, FieldReference field, VariableReader value) {
|
public void putField(VariableReader instance, FieldReference field, VariableReader value,
|
||||||
|
ValueType fieldType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -280,7 +280,7 @@ public class InstructionStringifier implements InstructionReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putField(VariableReader instance, FieldReference field, VariableReader value) {
|
public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) {
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
sb.append("@").append(instance.getIndex());
|
sb.append("@").append(instance.getIndex());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -398,11 +398,13 @@ public final class ProgramUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putField(VariableReader instance, FieldReference field, VariableReader value) {
|
public void putField(VariableReader instance, FieldReference field, VariableReader value,
|
||||||
|
ValueType fieldType) {
|
||||||
PutFieldInstruction insnCopy = new PutFieldInstruction();
|
PutFieldInstruction insnCopy = new PutFieldInstruction();
|
||||||
insnCopy.setField(field);
|
insnCopy.setField(field);
|
||||||
insnCopy.setInstance(instance != null ? copyVar(instance) : null);
|
insnCopy.setInstance(instance != null ? copyVar(instance) : null);
|
||||||
insnCopy.setValue(copyVar(value));
|
insnCopy.setValue(copyVar(value));
|
||||||
|
insnCopy.setFieldType(fieldType);
|
||||||
copy = insnCopy;
|
copy = insnCopy;
|
||||||
copy.setLocation(location);
|
copy.setLocation(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1611,6 +1611,7 @@ public class ProgramParser implements VariableDebugInformation {
|
||||||
insn.setInstance(getVariable(instance));
|
insn.setInstance(getVariable(instance));
|
||||||
insn.setField(new FieldReference(ownerCls, name));
|
insn.setField(new FieldReference(ownerCls, name));
|
||||||
insn.setValue(getVariable(value));
|
insn.setValue(getVariable(value));
|
||||||
|
insn.setFieldType(ValueType.parse(desc));
|
||||||
addInstruction(insn);
|
addInstruction(insn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,8 @@ class ProgramSourceAggregator implements InstructionReader {
|
||||||
@Override public void create(VariableReader receiver, String type) { }
|
@Override public void create(VariableReader receiver, String type) { }
|
||||||
@Override public void getField(VariableReader receiver, VariableReader instance, FieldReference field,
|
@Override public void getField(VariableReader receiver, VariableReader instance, FieldReference field,
|
||||||
ValueType fieldType) { }
|
ValueType fieldType) { }
|
||||||
@Override public void putField(VariableReader instance, FieldReference field, VariableReader value) { }
|
@Override public void putField(VariableReader instance, FieldReference field, VariableReader value,
|
||||||
|
ValueType fieldType) { }
|
||||||
@Override public void arrayLength(VariableReader receiver, VariableReader array) { }
|
@Override public void arrayLength(VariableReader receiver, VariableReader array) { }
|
||||||
@Override public void cloneArray(VariableReader receiver, VariableReader array) { }
|
@Override public void cloneArray(VariableReader receiver, VariableReader array) { }
|
||||||
@Override public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { }
|
@Override public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user