/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.compilers.opt.escape;
import static org.jikesrvm.compilers.opt.driver.OptConstants.MAYBE;
import static org.jikesrvm.compilers.opt.driver.OptConstants.YES;
import static org.jikesrvm.compilers.opt.ir.IRTools.IC;
import static org.jikesrvm.compilers.opt.ir.Operators.BOUNDS_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CHECKCAST_NOTNULL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CHECKCAST_UNRESOLVED_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CHECKCAST_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.GET_OBJ_TIB_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.GUARD_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_NOTNULL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_UNRESOLVED_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.NEWARRAY;
import static org.jikesrvm.compilers.opt.ir.Operators.NEWOBJMULTIARRAY_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.NULL_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.OBJARRAY_STORE_CHECK_NOTNULL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.OBJARRAY_STORE_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_IFCMP_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_MOVE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.TRAP;
import static org.jikesrvm.compilers.opt.ir.Operators.TRAP_IF;
import static org.jikesrvm.compilers.opt.ir.Operators.UBYTE_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.USHORT_ALOAD_opcode;
import java.util.HashSet;
import java.util.Set;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.ClassLoaderProxy;
import org.jikesrvm.compilers.opt.DefUse;
import org.jikesrvm.compilers.opt.OptimizingCompilerException;
import org.jikesrvm.compilers.opt.ir.ALoad;
import org.jikesrvm.compilers.opt.ir.AStore;
import org.jikesrvm.compilers.opt.ir.BoundsCheck;
import org.jikesrvm.compilers.opt.ir.CondMove;
import org.jikesrvm.compilers.opt.ir.GuardedUnary;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.IRTools;
import org.jikesrvm.compilers.opt.ir.InstanceOf;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.Move;
import org.jikesrvm.compilers.opt.ir.NewArray;
import org.jikesrvm.compilers.opt.ir.NullCheck;
import org.jikesrvm.compilers.opt.ir.Operator;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.Trap;
import org.jikesrvm.compilers.opt.ir.TrapIf;
import org.jikesrvm.compilers.opt.ir.TypeCheck;
import org.jikesrvm.compilers.opt.ir.operand.ConditionOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.ir.operand.TIBConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.TrapCodeOperand;
import org.jikesrvm.compilers.opt.ir.operand.TrueGuardOperand;
/**
* Class that performs scalar replacement of short arrays
*/
final class ShortArrayReplacer implements AggregateReplacer {
/**
* number of elements in the array
*/
private final int size;
/**
* type of the array
*/
private final RVMArray vmArray;
/**
* the register holding the array reference
*/
private final Register reg;
/**
* the governing IR
*/
private final IR ir;
/**
* @param r the register holding the array reference
* @param a the type of the array to replace
* @param s the size of the array to replace
* @param i the IR
*/
private ShortArrayReplacer(Register r, RVMArray a, int s, IR i) {
reg = r;
vmArray = a;
size = s;
ir = i;
}
/**
* Returns an object representing this transformation for a given
* allocation site.
*
* @param inst the allocation site
* @param ir the governing IR
* @return the object, or {@code null} if illegal
*/
public static ShortArrayReplacer getReplacer(Instruction inst, IR ir) {
if (inst.operator() != NEWARRAY) {
return null;
}
Operand size = NewArray.getSize(inst);
if (!size.isIntConstant()) {
return null;
}
int s = size.asIntConstant().value;
if (s > ir.options.ESCAPE_MAX_ARRAY_SIZE) {
return null;
}
if (s < 0) {
return null;
}
Register r = NewArray.getResult(inst).getRegister();
RVMArray a = NewArray.getType(inst).getVMType().asArray();
// TODO :handle these cases
if (containsUnsupportedUse(ir, r, s, a, null)) {
return null;
}
return new ShortArrayReplacer(r, a, s, ir);
}
@Override
public void transform() {
// first set up temporary scalars for the array elements
// initialize them before the def.
RegisterOperand[] scalars = new RegisterOperand[size];
TypeReference elementType = vmArray.getElementType().getTypeRef();
RegisterOperand def = reg.defList;
Instruction defI = def.instruction;
Operand defaultValue = IRTools.getDefaultOperand(elementType);
for (int i = 0; i < size; i++) {
scalars[i] = IRTools.moveIntoRegister(elementType, IRTools.getMoveOp(elementType), ir.regpool, defI, defaultValue.copy());
}
transform2(this.reg, defI, scalars);
}
private void transform2(Register reg, Instruction defI, RegisterOperand[] scalars) {
final boolean DEBUG = false;
// now remove the def
if (DEBUG) {
System.out.println("Removing " + defI);
}
DefUse.removeInstructionAndUpdateDU(defI);
// now handle the uses
for (RegisterOperand use = reg.useList; use != null; use = use.getNext()) {
scalarReplace(use, scalars, null);
}
}
/**
* Replace a given use of an array with its scalar equivalent.
*
* @param use the use to replace
* @param scalars an array of scalar register operands to replace
* the array with
* @param visited TODO currently useless. Is this parameter
* necessary or should it be removed?
*/
private void scalarReplace(RegisterOperand use, RegisterOperand[] scalars, Set<Register> visited) {
Instruction inst = use.instruction;
RVMType type = vmArray.getElementType();
switch (inst.getOpcode()) {
case INT_ALOAD_opcode:
case LONG_ALOAD_opcode:
case FLOAT_ALOAD_opcode:
case DOUBLE_ALOAD_opcode:
case BYTE_ALOAD_opcode:
case UBYTE_ALOAD_opcode:
case USHORT_ALOAD_opcode:
case SHORT_ALOAD_opcode:
case REF_ALOAD_opcode: {
// Create use of scalar or eliminate unreachable instruction because
// of a trap
if (ALoad.getIndex(inst).isIntConstant()) {
Operator moveOp = IRTools.getMoveOp(type.getTypeRef());
int index = ALoad.getIndex(inst).asIntConstant().value;
if (index >= 0 && index < size) {
Instruction i2 = Move.create(moveOp, ALoad.getClearResult(inst), scalars[index].copyRO());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
} else {
if (VM.BuildForIA32) {
if (size == 0) {
DefUse.removeInstructionAndUpdateDU(inst);
} else if (size == 1) {
int index = 0;
Operator moveOp = IRTools.getMoveOp(type.getTypeRef());
Instruction i2 = Move.create(moveOp, ALoad.getClearResult(inst), scalars[index].copyRO());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
} else {
Operator moveOp = IRTools.getCondMoveOp(type.getTypeRef());
Instruction i2 = CondMove.create(moveOp, ALoad.getClearResult(inst),
ALoad.getClearIndex(inst), IC(0), ConditionOperand.EQUAL(),
scalars[0].copyRO(), scalars[1].copyRO());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
} else {
if (size == 1) {
int index = 0;
Operator moveOp = IRTools.getMoveOp(type.getTypeRef());
Instruction i2 = Move.create(moveOp, ALoad.getClearResult(inst), scalars[index].copyRO());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
}
}
}
break;
case INT_ASTORE_opcode:
case LONG_ASTORE_opcode:
case FLOAT_ASTORE_opcode:
case DOUBLE_ASTORE_opcode:
case BYTE_ASTORE_opcode:
case SHORT_ASTORE_opcode:
case REF_ASTORE_opcode: {
// Create move to scalar or eliminate unreachable instruction because
// of a trap
if (AStore.getIndex(inst).isIntConstant()) {
int index = AStore.getIndex(inst).asIntConstant().value;
if (index >= 0 && index < size) {
Operator moveOp = IRTools.getMoveOp(type.getTypeRef());
Instruction i2 = Move.create(moveOp, scalars[index].copyRO(), AStore.getClearValue(inst));
DefUse.replaceInstructionAndUpdateDU(inst, i2);
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
} else {
if (VM.BuildForIA32) {
if (size == 0) {
DefUse.removeInstructionAndUpdateDU(inst);
} else if (size == 1) {
int index = 0;
Operator moveOp = IRTools.getMoveOp(type.getTypeRef());
Instruction i2 = Move.create(moveOp, scalars[index].copyRO(), AStore.getClearValue(inst));
DefUse.replaceInstructionAndUpdateDU(inst, i2);
} else {
Operator moveOp = IRTools.getCondMoveOp(type.getTypeRef());
Operand value = AStore.getClearValue(inst);
Instruction i2 = CondMove.create(moveOp, scalars[0].copyRO(),
AStore.getIndex(inst), IC(0), ConditionOperand.EQUAL(),
value, scalars[0].copyRO());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
Instruction i3 = CondMove.create(moveOp, scalars[1].copyRO(),
AStore.getIndex(inst), IC(0), ConditionOperand.NOT_EQUAL(),
value, scalars[1].copyRO());
i2.insertAfter(i3);
DefUse.updateDUForNewInstruction(i3);
}
} else {
if (size == 1) {
int index = 0;
Operator moveOp = IRTools.getMoveOp(type.getTypeRef());
Instruction i2 = Move.create(moveOp, scalars[index].copyRO(), AStore.getClearValue(inst));
DefUse.replaceInstructionAndUpdateDU(inst, i2);
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
}
}
}
break;
case NULL_CHECK_opcode: {
// Null check on result of new array must succeed
Instruction i2 = Move.create(GUARD_MOVE, NullCheck.getClearGuardResult(inst), new TrueGuardOperand());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
break;
case BOUNDS_CHECK_opcode: {
// Remove or create trap as appropriate
Instruction i2 = TrapIf.create(TRAP_IF, BoundsCheck.getClearGuardResult(inst),
IC(size), BoundsCheck.getClearIndex(inst), ConditionOperand.LOWER_EQUAL(),
TrapCodeOperand.ArrayBounds());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
break;
case CHECKCAST_opcode:
case CHECKCAST_NOTNULL_opcode:
case CHECKCAST_UNRESOLVED_opcode: {
// We cannot handle removing the checkcast if the result of the
// checkcast test is unknown
TypeReference lhsType = TypeCheck.getType(inst).getTypeRef();
if (ClassLoaderProxy.includesType(lhsType, vmArray.getTypeRef()) == YES) {
if (visited == null) {
visited = new HashSet<Register>();
}
Register copy = TypeCheck.getResult(inst).getRegister();
if (!visited.contains(copy)) {
visited.add(copy);
transform2(copy, inst, scalars);
// NB will remove inst
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
} else {
Instruction i2 = Trap.create(TRAP, null, TrapCodeOperand.CheckCast());
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
}
break;
case INSTANCEOF_opcode:
case INSTANCEOF_NOTNULL_opcode:
case INSTANCEOF_UNRESOLVED_opcode: {
// We cannot handle removing the instanceof if the result of the
// instanceof test is unknown
TypeReference lhsType = InstanceOf.getType(inst).getTypeRef();
Instruction i2;
if (ClassLoaderProxy.includesType(lhsType, vmArray.getTypeRef()) == YES) {
i2 = Move.create(INT_MOVE, InstanceOf.getClearResult(inst), IC(1));
} else {
i2 = Move.create(INT_MOVE, InstanceOf.getClearResult(inst), IC(0));
}
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
break;
case GET_OBJ_TIB_opcode: {
Instruction i2 = Move.create(REF_MOVE, GuardedUnary.getClearResult(inst), new TIBConstantOperand(vmArray));
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
break;
case REF_MOVE_opcode: {
if (visited == null) {
visited = new HashSet<Register>();
}
Register copy = Move.getResult(inst).getRegister();
if (!visited.contains(copy)) {
visited.add(copy);
transform2(copy, inst, scalars);
// NB will remove inst
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
}
break;
default:
throw new OptimizingCompilerException("Unexpected instruction: " + inst);
}
}
/**
* Some cases we don't handle yet. TODO: handle them.
*
* @param ir the governing IR
* @param reg the register in question
* @param size the size of the array to scalar replace.
* @param vmArray the array to replace
* @param visited the registers that were already visited
* @return whether the IR contains an unsupported use
*/
private static boolean containsUnsupportedUse(IR ir, Register reg, int size, RVMArray vmArray, Set<Register> visited) {
// If an array is accessed by a non-constant integer, what's the maximum size of support array?
final int MAX_SIZE_FOR_VARIABLE_LOAD_STORE = VM.BuildForIA32 ? 2 : 1;
for (RegisterOperand use = reg.useList; use != null; use = use.getNext()) {
switch (use.instruction.getOpcode()) {
case REF_IFCMP_opcode:
// Comparison between the array reference we want to replace and
// another. TODO: this case is either always true or always false,
// we should optimize
case NEWOBJMULTIARRAY_opcode:
// dimensions array must be passed as an array argument to
// newobjmultiarray, common case of 2 arguments is handled without a
// dimensions array
case OBJARRAY_STORE_CHECK_opcode:
case OBJARRAY_STORE_CHECK_NOTNULL_opcode:
// TODO: create a store check that doesn't need an array argument
return true;
case CHECKCAST_opcode:
case CHECKCAST_NOTNULL_opcode:
case CHECKCAST_UNRESOLVED_opcode: {
// We cannot handle removing the checkcast if the result of the
// checkcast test is unknown
TypeReference lhsType = TypeCheck.getType(use.instruction).getTypeRef();
byte ans = ClassLoaderProxy.includesType(lhsType, vmArray.getTypeRef());
if (ans == MAYBE) {
return true;
} else if (ans == YES) {
// handle as a move
if (visited == null) {
visited = new HashSet<Register>();
}
Register copy = TypeCheck.getResult(use.instruction).getRegister();
if (!visited.contains(copy)) {
visited.add(copy);
if (containsUnsupportedUse(ir, copy, size, vmArray, visited)) {
return true;
}
}
}
}
break;
case INSTANCEOF_opcode:
case INSTANCEOF_NOTNULL_opcode:
case INSTANCEOF_UNRESOLVED_opcode: {
// We cannot handle removing the instanceof if the result of the
// instanceof test is unknown
TypeReference lhsType = InstanceOf.getType(use.instruction).getTypeRef();
if (ClassLoaderProxy.includesType(lhsType, vmArray.getTypeRef()) == MAYBE) {
return true;
}
}
break;
case INT_ASTORE_opcode:
case LONG_ASTORE_opcode:
case FLOAT_ASTORE_opcode:
case DOUBLE_ASTORE_opcode:
case BYTE_ASTORE_opcode:
case SHORT_ASTORE_opcode:
case REF_ASTORE_opcode:
// Don't handle registers as indexes
// TODO: support for registers if the size of the array is small (e.g. 1)
if (!AStore.getIndex(use.instruction).isIntConstant() && size > MAX_SIZE_FOR_VARIABLE_LOAD_STORE) {
return true;
}
break;
case INT_ALOAD_opcode:
case LONG_ALOAD_opcode:
case FLOAT_ALOAD_opcode:
case DOUBLE_ALOAD_opcode:
case BYTE_ALOAD_opcode:
case UBYTE_ALOAD_opcode:
case USHORT_ALOAD_opcode:
case SHORT_ALOAD_opcode:
case REF_ALOAD_opcode:
// Don't handle registers as indexes
// TODO: support for registers if the size of the array is small (e.g. 1)
if (!ALoad.getIndex(use.instruction).isIntConstant() && size > MAX_SIZE_FOR_VARIABLE_LOAD_STORE) {
return true;
}
break;
case REF_MOVE_opcode:
if (visited == null) {
visited = new HashSet<Register>();
}
Register copy = Move.getResult(use.instruction).getRegister();
if (!visited.contains(copy)) {
visited.add(copy);
if (containsUnsupportedUse(ir, copy, size, vmArray, visited)) {
return true;
}
}
break;
}
}
return false;
}
}