/*
* 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.BOOLEAN_CMP_ADDR_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.BOOLEAN_CMP_INT_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CALL_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.GETFIELD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.GET_OBJ_TIB_opcode;
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_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_STORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.MONITORENTER_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.MONITOREXIT_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.MUST_IMPLEMENT_INTERFACE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.NULL_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.PUTFIELD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.READ_CEILING;
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.TRAP;
import static org.jikesrvm.compilers.opt.ir.Operators.WRITE_FLOOR;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.FieldReference;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMField;
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.Empty;
import org.jikesrvm.compilers.opt.ir.GetField;
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.New;
import org.jikesrvm.compilers.opt.ir.Operator;
import org.jikesrvm.compilers.opt.ir.PutField;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.Trap;
import org.jikesrvm.compilers.opt.ir.TypeCheck;
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;
/**
* Class that performs scalar replacement of aggregates for non-array
* objects
*/
final class ObjectReplacer implements AggregateReplacer {
/**
* type of the object
*/
private final RVMClass klass;
/**
* the IR
*/
private final IR ir;
/**
* the register holding the object reference
*/
private final Register reg;
/**
* Return an object representing this transformation for a given
* allocation site
*
* @param inst the allocation site
* @param ir the governing IR
* @return the object, or null if illegal
*/
public static ObjectReplacer getReplacer(Instruction inst, IR ir) {
Register r = New.getResult(inst).getRegister();
RVMClass klass = New.getType(inst).getVMType().asClass();
// TODO :handle these cases
if (klass.hasFinalizer() || containsUnsupportedUse(ir, r, klass, null)) {
return null;
}
return new ObjectReplacer(r, klass, ir);
}
@Override
public void transform() {
// store the object's fields in a ArrayList
ArrayList<RVMField> fields = getFieldsAsArrayList(klass);
// create a scalar for each field. initialize the scalar to
// default values before the object's def
RegisterOperand[] scalars = new RegisterOperand[fields.size()];
RegisterOperand def = reg.defList;
Instruction defI = def.instruction;
for (int i = 0; i < fields.size(); i++) {
RVMField f = fields.get(i);
Operand defaultValue = IRTools.getDefaultOperand(f.getType());
scalars[i] = IRTools.moveIntoRegister(ir.regpool, defI, defaultValue);
scalars[i].setType(f.getType());
}
transform2(this.reg, defI, scalars, fields, null);
}
private void transform2(Register reg, Instruction defI, RegisterOperand[] scalars, ArrayList<RVMField> fields, Set<Register> visited) {
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, fields, visited);
}
}
/**
* Returns the instance fields of the object.
* @param klass the type of the object
* @return a list holding the instance fields of the object
*/
private static ArrayList<RVMField> getFieldsAsArrayList(RVMClass klass) {
ArrayList<RVMField> v = new ArrayList<RVMField>();
for (RVMField field : klass.getInstanceFields()) {
v.add(field);
}
return v;
}
/**
* @param r the register holding the object reference
* @param _klass the type of the object to replace
* @param i the IR
*/
private ObjectReplacer(Register r, RVMClass _klass, IR i) {
reg = r;
klass = _klass;
ir = i;
}
/**
* Replace a given use of a object with its scalar equivalent
*
* @param use the use to replace
* @param scalars an array of scalar register operands to replace
* the object's fields with
* @param fields the object's fields
* @param visited the registers that were already seen
*/
private void scalarReplace(RegisterOperand use, RegisterOperand[] scalars, ArrayList<RVMField> fields, Set<Register> visited) {
Instruction inst = use.instruction;
try {
switch (inst.getOpcode()) {
case PUTFIELD_opcode: {
FieldReference fr = PutField.getLocation(inst).getFieldRef();
if (VM.VerifyAssertions) VM._assert(fr.isResolved());
RVMField f = fr.peekResolvedField();
int index = fields.indexOf(f);
TypeReference type = scalars[index].getType();
Operator moveOp = IRTools.getMoveOp(type);
Instruction i = Move.create(moveOp, scalars[index].copyRO(), PutField.getClearValue(inst));
inst.insertBefore(i);
DefUse.removeInstructionAndUpdateDU(inst);
DefUse.updateDUForNewInstruction(i);
}
break;
case GETFIELD_opcode: {
FieldReference fr = GetField.getLocation(inst).getFieldRef();
if (VM.VerifyAssertions) VM._assert(fr.isResolved());
RVMField f = fr.peekResolvedField();
int index = fields.indexOf(f);
TypeReference type = scalars[index].getType();
Operator moveOp = IRTools.getMoveOp(type);
Instruction i = Move.create(moveOp, GetField.getClearResult(inst), scalars[index].copyRO());
inst.insertBefore(i);
DefUse.removeInstructionAndUpdateDU(inst);
DefUse.updateDUForNewInstruction(i);
}
break;
case MONITORENTER_opcode:
inst.insertBefore(Empty.create(READ_CEILING));
DefUse.removeInstructionAndUpdateDU(inst);
break;
case MONITOREXIT_opcode:
inst.insertBefore(Empty.create(WRITE_FLOOR));
DefUse.removeInstructionAndUpdateDU(inst);
break;
case CALL_opcode:
case NULL_CHECK_opcode:
// (SJF) TODO: Why wasn't this caught by BC2IR for
// java.lang.Double.<init> (Ljava/lang/String;)V ?
DefUse.removeInstructionAndUpdateDU(inst);
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, klass.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, fields, visited);
// 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, klass.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(klass));
DefUse.replaceInstructionAndUpdateDU(inst, i2);
}
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);
transform2(copy, inst, scalars, fields, visited);
// NB will remove inst
} else {
DefUse.removeInstructionAndUpdateDU(inst);
}
}
break;
default:
throw new OptimizingCompilerException("ObjectReplacer: unexpected use " + inst);
}
} catch (Exception e) {
OptimizingCompilerException oe = new OptimizingCompilerException("Error handling use (" + use + ") of: " + inst);
oe.initCause(e);
throw oe;
}
}
/**
* Some cases we don't handle yet. TODO: handle them.
*
* @param ir the IR to check
* @param reg the register whose uses are being checked
* @param klass the class of the newly created object
* @param visited registers that were already seen
*
* @return {@code true} if the IR contains a case that we don't handle yet
*/
private static boolean containsUnsupportedUse(IR ir, Register reg, RVMClass klass, Set<Register> visited) {
for (RegisterOperand use = reg.useList; use != null; use = use.getNext()) {
switch (use.instruction.getOpcode()) {
case MUST_IMPLEMENT_INTERFACE_opcode:
case REF_IFCMP_opcode:
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, klass.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, klass, 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, klass.getTypeRef()) == MAYBE) {
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, klass, visited)) {
return true;
}
}
break;
case BOOLEAN_CMP_INT_opcode:
case BOOLEAN_CMP_ADDR_opcode:
case LONG_STORE_opcode:
throw new OptimizingCompilerException("Unexpected use of reference considered for replacement: " + use.instruction + " in " + ir.method);
}
}
return false;
}
}