/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.Allegrex.compiler;
import static jpcsp.Allegrex.compiler.CompilerContext.executableDescriptor;
import static jpcsp.HLE.modules.ThreadManForUser.INTERNAL_THREAD_ADDRESS_END;
import static jpcsp.HLE.modules.ThreadManForUser.INTERNAL_THREAD_ADDRESS_START;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import jpcsp.Allegrex.Common.Instruction;
import jpcsp.Allegrex.compiler.nativeCode.HookCodeInstruction;
import jpcsp.Allegrex.compiler.nativeCode.NativeCodeInstruction;
import jpcsp.Allegrex.compiler.nativeCode.NativeCodeManager;
import jpcsp.Allegrex.compiler.nativeCode.NativeCodeSequence;
import jpcsp.HLE.HLEModuleFunction;
import jpcsp.HLE.HLEModuleManager;
import org.apache.log4j.Logger;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;
/**
* @author gid15
*
*/
public class CodeBlock {
private static Logger log = Compiler.log;
private int startAddress;
private int lowestAddress;
private int highestAddress;
private LinkedList<CodeInstruction> codeInstructions = new LinkedList<CodeInstruction>();
private LinkedList<SequenceCodeInstruction> sequenceCodeInstructions = new LinkedList<SequenceCodeInstruction>();
private SequenceCodeInstruction currentSequence = null;
private IExecutable executable = null;
private final static String objectInternalName = Type.getInternalName(Object.class);
private final static String[] interfacesForExecutable = new String[] { Type.getInternalName(IExecutable.class) };
private final static String[] exceptions = new String[] { Type.getInternalName(Exception.class) };
private int instanceIndex;
private Instruction[] interpretedInstructions;
private int[] interpretedOpcodes;
private MemoryRanges memoryRanges = new MemoryRanges();
private int flags;
private HLEModuleFunction hleFunction;
public CodeBlock(int startAddress, int instanceCount) {
this.startAddress = startAddress;
this.instanceIndex = instanceCount;
lowestAddress = startAddress;
highestAddress = startAddress;
}
public void addInstruction(int address, int opcode, Instruction insn, boolean isBranchTarget, boolean isBranching, int branchingTo) {
if (log.isTraceEnabled()) {
log.trace(String.format("CodeBlock.addInstruction 0x%X - %s", address, insn.disasm(address, opcode)));
}
CodeInstruction codeInstruction = new CodeInstruction(address, opcode, insn, isBranchTarget, isBranching, branchingTo);
// Insert the codeInstruction in the codeInstructions list
// and keep the list sorted by address.
if (codeInstructions.isEmpty() || codeInstructions.getLast().getAddress() < address) {
codeInstructions.add(codeInstruction);
} else {
for (ListIterator<CodeInstruction> lit = codeInstructions.listIterator(); lit.hasNext(); ) {
CodeInstruction listItem = lit.next();
if (listItem.getAddress() > address) {
lit.previous();
lit.add(codeInstruction);
break;
}
}
if (address < lowestAddress) {
lowestAddress = address;
}
}
if (address > highestAddress) {
highestAddress = address;
}
memoryRanges.addAddress(address);
flags |= insn.getFlags();
}
public void setIsBranchTarget(int address) {
if (log.isTraceEnabled()) {
log.trace("CodeBlock.setIsBranchTarget 0x" + Integer.toHexString(address).toUpperCase());
}
CodeInstruction codeInstruction = getCodeInstruction(address);
if (codeInstruction != null) {
codeInstruction.setBranchTarget(true);
}
}
public int getStartAddress() {
return startAddress;
}
public int getLowestAddress() {
return lowestAddress;
}
public int getHighestAddress() {
return highestAddress;
}
public int getLength() {
return (getHighestAddress() - getLowestAddress()) / 4 + 1;
}
public CodeInstruction getCodeInstruction(int address) {
if (currentSequence != null) {
return currentSequence.getCodeSequence().getCodeInstruction(address);
}
for (CodeInstruction codeInstruction : codeInstructions) {
if (codeInstruction.getAddress() == address) {
return codeInstruction;
}
}
return null;
}
public String getClassName() {
return CompilerContext.getClassName(getStartAddress(), getInstanceIndex());
}
public String getInternalClassName() {
return getInternalName(getClassName());
}
private String getInternalName(String name) {
return name.replace('.', '/');
}
@SuppressWarnings("unchecked")
private Class<IExecutable> loadExecutable(CompilerContext context, String className, byte[] bytes) throws ClassFormatError {
try {
// Try to define a new class for this executable.
return (Class<IExecutable>) context.getClassLoader().defineClass(className, bytes);
} catch (ClassFormatError e) {
// This exception is catched by the Compiler
throw e;
} catch (LinkageError le) {
// If the class already exists, try finding it in this context.
try {
return (Class<IExecutable>)context.getClassLoader().findClass(className);
} catch (ClassNotFoundException cnfe) {
// Return null if none of the above work.
return null;
}
}
}
private void addConstructor(ClassVisitor cv) {
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, objectInternalName, "<init>", "()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private void addNonStaticMethods(CompilerContext context, ClassVisitor cv) {
MethodVisitor mv;
// public int exec(int returnAddress, int alternativeReturnAddress, boolean isJump) throws Exception;
mv = cv.visitMethod(Opcodes.ACC_PUBLIC, context.getExecMethodName(), context.getExecMethodDesc(), null, exceptions);
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, getClassName(), context.getStaticExecMethodName(), context.getStaticExecMethodDesc());
mv.visitInsn(Opcodes.IRETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// private static IExecutable e;
FieldVisitor fv = cv.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, context.getReplaceFieldName(), executableDescriptor, null, null);
fv.visitEnd();
// public void setExecutable(IExecutable e);
mv = cv.visitMethod(Opcodes.ACC_PUBLIC, context.getReplaceMethodName(), context.getReplaceMethodDesc(), null, exceptions);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitFieldInsn(Opcodes.PUTSTATIC, getClassName(), context.getReplaceFieldName(), executableDescriptor);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 2);
mv.visitEnd();
// public IExecutable getExecutable();
mv = cv.visitMethod(Opcodes.ACC_PUBLIC, context.getGetMethodName(), context.getGetMethodDesc(), null, exceptions);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, getClassName(), context.getReplaceFieldName(), executableDescriptor);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private void addCodeSequence(List<CodeSequence> codeSequences, CodeSequence codeSequence) {
if (codeSequence != null) {
if (codeSequence.getLength() > 1) {
codeSequences.add(codeSequence);
}
}
}
private void generateCodeSequences(List<CodeSequence> codeSequences, int sequenceMaxInstructions) {
CodeSequence currentCodeSequence = null;
int nextAddress = 0;
final int sequenceMaxInstructionsWithDelay = sequenceMaxInstructions - 1;
for (CodeInstruction codeInstruction : codeInstructions) {
int address = codeInstruction.getAddress();
if (address < nextAddress) {
// Skip it
} else {
if (codeInstruction.hasFlags(Instruction.FLAG_CANNOT_BE_SPLIT)) {
addCodeSequence(codeSequences, currentCodeSequence);
currentCodeSequence = null;
if (codeInstruction.hasFlags(Instruction.FLAG_HAS_DELAY_SLOT)) {
nextAddress = address + 8;
}
} else if (codeInstruction.isBranchTarget()) {
addCodeSequence(codeSequences, currentCodeSequence);
currentCodeSequence = new CodeSequence(address);
} else {
if (currentCodeSequence == null) {
currentCodeSequence = new CodeSequence(address);
} else if (currentCodeSequence.getLength() + codeInstruction.getLength() > sequenceMaxInstructionsWithDelay) {
boolean doSplit = false;
if (currentCodeSequence.getLength() + codeInstruction.getLength() > sequenceMaxInstructions) {
doSplit = true;
} else if (codeInstruction.hasFlags(Instruction.FLAG_HAS_DELAY_SLOT)) {
doSplit = true;
}
if (doSplit) {
addCodeSequence(codeSequences, currentCodeSequence);
currentCodeSequence = new CodeSequence(address);
}
}
currentCodeSequence.setEndAddress(codeInstruction.getEndAddress());
}
}
}
addCodeSequence(codeSequences, currentCodeSequence);
}
private CodeSequence findCodeSequence(CodeInstruction codeInstruction, List<CodeSequence> codeSequences, CodeSequence currentCodeSequence) {
int address = codeInstruction.getAddress();
if (currentCodeSequence != null) {
if (currentCodeSequence.isInside(address)) {
return currentCodeSequence;
}
}
for (CodeSequence codeSequence : codeSequences) {
if (codeSequence.isInside(address)) {
return codeSequence;
}
}
return null;
}
private void splitCodeSequences(CompilerContext context, int methodMaxInstructions) {
List<CodeSequence> codeSequences = new ArrayList<CodeSequence>();
generateCodeSequences(codeSequences, methodMaxInstructions);
Collections.sort(codeSequences);
int currentMethodInstructions = codeInstructions.size();
List<CodeSequence> sequencesToBeSplit = new ArrayList<CodeSequence>();
for (CodeSequence codeSequence : codeSequences) {
sequencesToBeSplit.add(codeSequence);
if (log.isDebugEnabled()) {
log.debug("Sequence to be split: " + codeSequence.toString());
}
currentMethodInstructions -= codeSequence.getLength();
if (currentMethodInstructions <= methodMaxInstructions) {
break;
}
}
CodeSequence currentCodeSequence = null;
for (ListIterator<CodeInstruction> lit = codeInstructions.listIterator(); lit.hasNext(); ) {
CodeInstruction codeInstruction = lit.next();
CodeSequence codeSequence = findCodeSequence(codeInstruction, sequencesToBeSplit, currentCodeSequence);
if (codeSequence != null) {
lit.remove();
if (codeSequence.getInstructions().isEmpty()) {
codeSequence.addInstruction(codeInstruction);
SequenceCodeInstruction sequenceCodeInstruction = new SequenceCodeInstruction(codeSequence);
lit.add(sequenceCodeInstruction);
sequenceCodeInstructions.add(sequenceCodeInstruction);
} else {
codeSequence.addInstruction(codeInstruction);
}
currentCodeSequence = codeSequence;
}
}
}
private void scanNativeCodeSequences(CompilerContext context) {
NativeCodeManager nativeCodeManager = context.getNativeCodeManager();
for (ListIterator<CodeInstruction> lit = codeInstructions.listIterator(); lit.hasNext(); ) {
CodeInstruction codeInstruction = lit.next();
NativeCodeSequence nativeCodeSequence = nativeCodeManager.getNativeCodeSequence(codeInstruction, this);
if (nativeCodeSequence != null) {
if (nativeCodeSequence.isHook()) {
HookCodeInstruction hookCodeInstruction = new HookCodeInstruction(nativeCodeSequence, codeInstruction);
// Replace the current code instruction by the hook code instruction
lit.remove();
lit.add(hookCodeInstruction);
} else {
NativeCodeInstruction nativeCodeInstruction = new NativeCodeInstruction(codeInstruction.getAddress(), nativeCodeSequence);
if (nativeCodeInstruction.isBranching()) {
setIsBranchTarget(nativeCodeInstruction.getBranchingTo());
}
if (nativeCodeSequence.isWholeCodeBlock()) {
codeInstructions.clear();
codeInstructions.add(nativeCodeInstruction);
} else {
// Remove the first opcode that started this native code sequence
lit.remove();
// Add any code instructions that need to be inserted before
// the native code sequence
List<CodeInstruction> beforeCodeInstructions = nativeCodeSequence.getBeforeCodeInstructions();
if (beforeCodeInstructions != null) {
for (CodeInstruction beforeCodeInstruction : beforeCodeInstructions) {
CodeInstruction newCodeInstruction = new CodeInstruction(beforeCodeInstruction);
newCodeInstruction.setAddress(codeInstruction.getAddress());
lit.add(newCodeInstruction);
}
}
// Add the native code sequence itself
lit.add(nativeCodeInstruction);
// Remove the further opcodes from the native code sequence
for (int i = nativeCodeSequence.getNumOpcodes() - 1; i > 0 && lit.hasNext(); i--) {
lit.next();
lit.remove();
}
}
}
}
}
}
private void prepare(CompilerContext context, int methodMaxInstructions) {
memoryRanges.updateValues();
scanNativeCodeSequences(context);
if (codeInstructions.size() > methodMaxInstructions) {
if (log.isInfoEnabled()) {
log.info("Splitting " + getClassName() + " (" + codeInstructions.size() + "/" + methodMaxInstructions + ")");
}
splitCodeSequences(context, methodMaxInstructions);
}
}
private void compile(CompilerContext context, MethodVisitor mv, List<CodeInstruction> codeInstructions) {
context.optimizeSequence(codeInstructions);
int numberInstructionsToBeSkipped = 0;
for (CodeInstruction codeInstruction : codeInstructions) {
if (numberInstructionsToBeSkipped > 0) {
if (!context.isSkipDelaySlot() && codeInstruction.isBranchTarget()) {
context.compileDelaySlotAsBranchTarget(codeInstruction);
}
numberInstructionsToBeSkipped--;
if (numberInstructionsToBeSkipped <= 0) {
context.skipInstructions(0, false);
}
} else {
codeInstruction.compile(context, mv);
numberInstructionsToBeSkipped = context.getNumberInstructionsToBeSkipped();
}
}
}
private Class<IExecutable> interpret(CompilerContext context) {
Class<IExecutable> compiledClass = null;
context.setCodeBlock(this);
String className = getInternalClassName();
if (log.isInfoEnabled()) {
log.info("Compiling for Interpreter " + className);
}
int computeFlag = ClassWriter.COMPUTE_FRAMES;
if (context.isAutomaticMaxLocals() || context.isAutomaticMaxStack()) {
computeFlag |= ClassWriter.COMPUTE_MAXS;
}
ClassWriter cw = new ClassWriter(computeFlag);
ClassVisitor cv = cw;
if (log.isDebugEnabled()) {
cv = new CheckClassAdapter(cv);
}
StringWriter debugOutput = null;
if (log.isDebugEnabled()) {
debugOutput = new StringWriter();
PrintWriter debugPrintWriter = new PrintWriter(debugOutput);
cv = new TraceClassVisitor(cv, debugPrintWriter);
}
cv.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null, objectInternalName, interfacesForExecutable);
context.startClass(cv);
addConstructor(cv);
addNonStaticMethods(context, cv);
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, context.getStaticExecMethodName(), context.getStaticExecMethodDesc(), null, exceptions);
mv.visitCode();
context.setMethodVisitor(mv);
context.startMethod();
context.compileExecuteInterpreter(getStartAddress());
mv.visitMaxs(context.getMaxStack(), context.getMaxLocals());
mv.visitEnd();
cv.visitEnd();
if (debugOutput != null) {
log.debug(debugOutput.toString());
}
compiledClass = loadExecutable(context, className, cw.toByteArray());
return compiledClass;
}
private Class<IExecutable> compile(CompilerContext context) throws ClassFormatError {
Class<IExecutable> compiledClass = null;
context.setCodeBlock(this);
String className = getInternalClassName();
if (log.isDebugEnabled()) {
String hleFunctionName = null;
HLEModuleFunction func = HLEModuleManager.getInstance().getFunctionFromAddress(getStartAddress());
if (func != null) {
hleFunctionName = func.getFunctionName();
}
if (hleFunctionName != null) {
log.debug(String.format("Compiling %s (%s)", className, hleFunctionName));
} else {
log.debug(String.format("Compiling %s", className));
}
}
prepare(context, context.getMethodMaxInstructions());
currentSequence = null;
int computeFlag = ClassWriter.COMPUTE_FRAMES;
if (context.isAutomaticMaxLocals() || context.isAutomaticMaxStack()) {
computeFlag |= ClassWriter.COMPUTE_MAXS;
}
ClassWriter cw = new ClassWriter(computeFlag);
ClassVisitor cv = cw;
if (log.isDebugEnabled()) {
cv = new CheckClassAdapter(cv);
}
StringWriter debugOutput = null;
if (log.isTraceEnabled()) {
debugOutput = new StringWriter();
PrintWriter debugPrintWriter = new PrintWriter(debugOutput);
cv = new TraceClassVisitor(cv, debugPrintWriter);
}
cv.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null, objectInternalName, interfacesForExecutable);
context.startClass(cv);
addConstructor(cv);
addNonStaticMethods(context, cv);
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, context.getStaticExecMethodName(), context.getStaticExecMethodDesc(), null, exceptions);
mv.visitCode();
context.setMethodVisitor(mv);
context.startMethod();
// Jump to the block start if other instructions have been inserted in front
if (!codeInstructions.isEmpty() && codeInstructions.getFirst().getAddress() != getStartAddress()) {
mv.visitJumpInsn(Opcodes.GOTO, getCodeInstruction(getStartAddress()).getLabel());
}
compile(context, mv, codeInstructions);
mv.visitMaxs(context.getMaxStack(), context.getMaxLocals());
mv.visitEnd();
for (SequenceCodeInstruction sequenceCodeInstruction : sequenceCodeInstructions) {
if (log.isDebugEnabled()) {
log.debug("Compiling Sequence " + sequenceCodeInstruction.getMethodName(context));
}
currentSequence = sequenceCodeInstruction;
mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, sequenceCodeInstruction.getMethodName(context), "()V", null, exceptions);
mv.visitCode();
context.setMethodVisitor(mv);
context.startSequenceMethod();
compile(context, mv, sequenceCodeInstruction.getCodeSequence().getInstructions());
context.endSequenceMethod();
mv.visitMaxs(context.getMaxStack(), context.getMaxLocals());
mv.visitEnd();
}
currentSequence = null;
cv.visitEnd();
if (debugOutput != null) {
log.trace(debugOutput.toString());
}
try {
compiledClass = loadExecutable(context, className, cw.toByteArray());
} catch (NullPointerException e) {
log.error("Error while compiling " + className + ": " + e);
}
return compiledClass;
}
public IExecutable getExecutable() {
return executable;
}
public synchronized IExecutable getExecutable(CompilerContext context) throws ClassFormatError {
if (executable == null) {
Class<IExecutable> classExecutable = compile(context);
if (classExecutable != null) {
try {
executable = classExecutable.newInstance();
} catch (InstantiationException e) {
log.error(e);
} catch (IllegalAccessException e) {
log.error(e);
}
}
}
return executable;
}
public synchronized IExecutable getInterpretedExecutable(CompilerContext context) {
if (executable == null) {
Class<IExecutable> classExecutable = interpret(context);
if (classExecutable != null) {
try {
executable = classExecutable.newInstance();
} catch (InstantiationException e) {
log.error(e);
} catch (IllegalAccessException e) {
log.error(e);
}
}
}
return executable;
}
public int getInstanceIndex() {
return instanceIndex;
}
public int getNewInstanceIndex() {
instanceIndex++;
return instanceIndex;
}
public Instruction[] getInterpretedInstructions() {
return interpretedInstructions;
}
public void setInterpretedInstructions(Instruction[] interpretedInstructions) {
this.interpretedInstructions = interpretedInstructions;
}
public int[] getInterpretedOpcodes() {
return interpretedOpcodes;
}
public void setInterpretedOpcodes(int[] interpretedOpcodes) {
this.interpretedOpcodes = interpretedOpcodes;
}
public boolean areOpcodesChanged() {
return memoryRanges.areValuesChanged();
}
public boolean isOverlappingWithAddressRange(int address, int size) {
return memoryRanges.isOverlappingWithAddressRange(address, size);
}
public boolean isInternal() {
int addr = getStartAddress();
return addr < INTERNAL_THREAD_ADDRESS_END && addr >= INTERNAL_THREAD_ADDRESS_START;
}
public int getFlags() {
return flags;
}
public boolean hasFlags(int testFlags) {
return (flags & testFlags) == testFlags;
}
public void addCodeBlock() {
RuntimeContext.addCodeBlock(startAddress, this);
}
public HLEModuleFunction getHLEFunction() {
return hleFunction;
}
public void setHLEFunction(HLEModuleFunction hleFunction) {
this.hleFunction = hleFunction;
}
public boolean isHLEFunction() {
return hleFunction != null;
}
@Override
public String toString() {
return String.format("CodeBlock 0x%08X[0x%08X-0x%08X]", getStartAddress(), getLowestAddress(), getHighestAddress());
}
}