/*
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.nativeCode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jpcsp.Allegrex.Common;
import jpcsp.Allegrex.Decoder;
import jpcsp.Allegrex.compiler.CodeBlock;
import jpcsp.Allegrex.compiler.CodeInstruction;
import jpcsp.Allegrex.compiler.Compiler;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.MemoryReader;
import jpcsp.util.Utilities;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author gid15
*
*/
public class NativeCodeManager {
private static int defaultOpcodeMask = 0xFFFFFFFF;
private HashMap<Integer, List<NativeCodeSequence>> nativeCodeSequencesByFirstOpcode;
private List<NativeCodeSequence> nativeCodeSequenceWithMaskInFirstOpcode;
private HashMap<Integer, NativeCodeSequence> compiledNativeCodeBlocks;
public NativeCodeManager(Element configuration) {
compiledNativeCodeBlocks = new HashMap<Integer, NativeCodeSequence>();
nativeCodeSequencesByFirstOpcode = new HashMap<Integer, List<NativeCodeSequence>>();
nativeCodeSequenceWithMaskInFirstOpcode = new LinkedList<NativeCodeSequence>();
load(configuration);
}
public void reset() {
compiledNativeCodeBlocks.clear();
}
@SuppressWarnings("unchecked")
private Class<INativeCodeSequence> getNativeCodeSequenceClass(String className) {
try {
return (Class<INativeCodeSequence>) Class.forName(className);
} catch (ClassNotFoundException e) {
Compiler.log.error(e);
return null;
}
}
private String getContent(Node node) {
if (node.hasChildNodes()) {
return getContent(node.getChildNodes());
}
return node.getNodeValue();
}
private String getContent(NodeList nodeList) {
if (nodeList == null || nodeList.getLength() <= 0) {
return null;
}
StringBuilder content = new StringBuilder();
int n = nodeList.getLength();
for (int i = 0; i < n; i++) {
Node node = nodeList.item(i);
content.append(getContent(node));
}
return content.toString();
}
private void loadBeforeCodeInstructions(NativeCodeSequence nativeCodeSequence, String codeInstructions) {
BufferedReader reader = new BufferedReader(new StringReader(codeInstructions));
if (reader == null) {
return;
}
Pattern codeInstructionPattern = Pattern.compile("\\s*(\\w+\\s*:?\\s*)?\\[(\\p{XDigit}+)\\].*");
final int opcodeGroup = 2;
final int address = 0;
try {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
line = line.trim();
if (line.length() > 0) {
try {
Matcher codeInstructionMatcher = codeInstructionPattern.matcher(line);
int opcode = 0;
if (codeInstructionMatcher.matches()) {
opcode = Utilities.parseAddress(codeInstructionMatcher.group(opcodeGroup));
} else {
opcode = Utilities.parseAddress(line.trim());
}
Common.Instruction insn = Decoder.instruction(opcode);
CodeInstruction codeInstruction = new CodeInstruction(address, opcode, insn, false, false, 0);
nativeCodeSequence.addBeforeCodeInstruction(codeInstruction);
} catch (NumberFormatException e) {
Compiler.log.error(e);
}
}
}
} catch (IOException e) {
Compiler.log.error(e);
}
}
private void loadNativeCodeOpcodes(NativeCodeSequence nativeCodeSequence, String codeInstructions) {
BufferedReader reader = new BufferedReader(new StringReader(codeInstructions));
if (reader == null) {
return;
}
Pattern codeInstructionPattern = Pattern.compile("\\s*((\\w+)\\s*:?\\s*)?\\[(\\p{XDigit}+)(/(\\p{XDigit}+))?\\].*");
final int labelGroup = 2;
final int opcodeGroup = 3;
final int opcodeMaskGroup = 5;
try {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
line = line.trim();
if (line.length() > 0) {
try {
Matcher codeInstructionMatcher = codeInstructionPattern.matcher(line);
int opcode = 0;
int mask = defaultOpcodeMask;
String label = null;
if (codeInstructionMatcher.matches()) {
opcode = Utilities.parseAddress(codeInstructionMatcher.group(opcodeGroup));
String opcodeMaskString = codeInstructionMatcher.group(opcodeMaskGroup);
if (opcodeMaskString != null) {
mask = Utilities.parseAddress(opcodeMaskString);
}
label = codeInstructionMatcher.group(labelGroup);
} else {
opcode = Utilities.parseAddress(line.trim());
}
nativeCodeSequence.addOpcode(opcode, mask, label);
} catch (NumberFormatException e) {
Compiler.log.error(e);
}
}
}
} catch (IOException e) {
Compiler.log.error(e);
}
}
private void setParameter(NativeCodeSequence nativeCodeSequence, int parameter, String valueString) {
if (valueString == null || valueString.length() <= 0) {
nativeCodeSequence.setParameter(parameter, 0, false);
return;
}
for (int i = 0; i < Common.gprNames.length; i++) {
if (Common.gprNames[i].equals(valueString)) {
nativeCodeSequence.setParameter(parameter, i, false);
return;
}
}
for (int i = 0; i < Common.fprNames.length; i++) {
if (Common.fprNames[i].equals(valueString)) {
nativeCodeSequence.setParameter(parameter, i, false);
return;
}
}
if (valueString.startsWith("@")) {
String label = valueString.substring(1);
int labelIndex = nativeCodeSequence.getLabelIndex(label);
if (labelIndex >= 0) {
nativeCodeSequence.setParameter(parameter, labelIndex, true);
return;
}
}
try {
int value;
if (valueString.startsWith("0x")) {
value = Integer.parseInt(valueString.substring(2), 16);
} else {
value = Integer.parseInt(valueString);
}
nativeCodeSequence.setParameter(parameter, value, false);
} catch (NumberFormatException e) {
Compiler.log.error(e);
}
}
private void loadNativeCodeSequence(Element element) {
String name = element.getAttribute("name");
String className = getContent(element.getElementsByTagName("Class"));
Class<INativeCodeSequence> nativeCodeSequenceClass = getNativeCodeSequenceClass(className);
if (nativeCodeSequenceClass == null) {
return;
}
NativeCodeSequence nativeCodeSequence = new NativeCodeSequence(name, nativeCodeSequenceClass);
String isReturningString = getContent(element.getElementsByTagName("IsReturning"));
if (isReturningString != null) {
nativeCodeSequence.setReturning(Boolean.parseBoolean(isReturningString));
}
String wholeCodeBlockString = getContent(element.getElementsByTagName("WholeCodeBlock"));
if (wholeCodeBlockString != null) {
nativeCodeSequence.setWholeCodeBlock(Boolean.parseBoolean(wholeCodeBlockString));
}
String methodName = getContent(element.getElementsByTagName("Method"));
if (methodName != null) {
nativeCodeSequence.setMethodName(methodName);
}
String isHookString = getContent(element.getElementsByTagName("IsHook"));
if (isHookString != null) {
nativeCodeSequence.setHook(Boolean.parseBoolean(isHookString));
}
String isMethodRetuningString = getContent(element.getElementsByTagName("IsMethodReturning"));
if (isMethodRetuningString != null) {
nativeCodeSequence.setMethodReturning(Boolean.parseBoolean(isMethodRetuningString));
}
String codeInstructions = getContent(element.getElementsByTagName("CodeInstructions"));
loadNativeCodeOpcodes(nativeCodeSequence, codeInstructions);
// The "Parameters" and "BranchInstruction" have to be parsed after "CodeInstructions"
// because they are using them (e.g. instruction labels)
String parametersList = getContent(element.getElementsByTagName("Parameters"));
if (parametersList != null) {
String[] parameters = parametersList.split(" *, *");
for (int parameter = 0; parameters != null && parameter < parameters.length; parameter++) {
setParameter(nativeCodeSequence, parameter, parameters[parameter].trim());
}
}
String branchInstructionLabel = getContent(element.getElementsByTagName("BranchInstruction"));
if (branchInstructionLabel != null) {
if (branchInstructionLabel.startsWith("@")) {
branchInstructionLabel = branchInstructionLabel.substring(1);
}
int branchInstructionOffset = nativeCodeSequence.getLabelIndex(branchInstructionLabel.trim());
if (branchInstructionOffset >= 0) {
nativeCodeSequence.setBranchInstruction(branchInstructionOffset);
} else {
Compiler.log.error(String.format("BranchInstruction: label '%s' not found", branchInstructionLabel));
}
}
String beforeCodeInstructions = getContent(element.getElementsByTagName("BeforeCodeInstructions"));
if (beforeCodeInstructions != null) {
loadBeforeCodeInstructions(nativeCodeSequence, beforeCodeInstructions);
}
addNativeCodeSequence(nativeCodeSequence);
}
private void load(Element configuration) {
if (configuration == null) {
return;
}
NodeList nativeCodeBlocks = configuration.getElementsByTagName("NativeCodeSequence");
int n = nativeCodeBlocks.getLength();
for (int i = 0; i < n; i++) {
Element nativeCodeSequence = (Element) nativeCodeBlocks.item(i);
loadNativeCodeSequence(nativeCodeSequence);
}
}
private void addNativeCodeSequence(NativeCodeSequence nativeCodeSequence) {
if (nativeCodeSequence.getNumOpcodes() > 0) {
int firstOpcodeMask = nativeCodeSequence.getFirstOpcodeMask();
if (firstOpcodeMask == defaultOpcodeMask) {
// First opcode has not mask: fast lookup allowed
int firstOpcode = nativeCodeSequence.getFirstOpcode();
if (!nativeCodeSequencesByFirstOpcode.containsKey(firstOpcode)) {
nativeCodeSequencesByFirstOpcode.put(firstOpcode, new LinkedList<NativeCodeSequence>());
}
nativeCodeSequencesByFirstOpcode.get(firstOpcode).add(nativeCodeSequence);
} else {
// First opcode has not mask: only slow lookup possible
nativeCodeSequenceWithMaskInFirstOpcode.add(nativeCodeSequence);
}
}
}
public void setCompiledNativeCodeBlock(int address, NativeCodeSequence nativeCodeBlock) {
compiledNativeCodeBlocks.put(address, nativeCodeBlock);
}
public NativeCodeSequence getCompiledNativeCodeBlock(int address) {
return compiledNativeCodeBlocks.get(address);
}
public void invalidateCompiledNativeCodeBlocks(int startAddress, int endAddress) {
// Most common case: nothing to do.
if (compiledNativeCodeBlocks.size() == 0) {
return;
}
// What is the most efficient?
// To scan all the addressed between startAddress and endAddres or
// to scan all the compiledNativeCodeBlocks?
if ((endAddress - startAddress) >>> 2 < compiledNativeCodeBlocks.size()) {
for (int address = startAddress; address <= endAddress; address += 4) {
compiledNativeCodeBlocks.remove(address);
}
} else {
List<Integer> toBeRemoved = new LinkedList<Integer>();
for (Integer address : compiledNativeCodeBlocks.keySet()) {
if (startAddress >= address.intValue() && address.intValue() <= endAddress) {
toBeRemoved.add(address);
}
}
for (Integer address : toBeRemoved) {
compiledNativeCodeBlocks.remove(address);
}
toBeRemoved.clear();
}
}
private boolean isNativeCodeSequence(NativeCodeSequence nativeCodeSequence, CodeInstruction codeInstruction, CodeBlock codeBlock) {
int address = codeInstruction.getAddress();
int numOpcodes = nativeCodeSequence.getNumOpcodes();
// Can this NativeCodeSequence only match a whole CodeBlock?
if (nativeCodeSequence.isWholeCodeBlock()) {
// Match only a whole CodeBlock: same StartAddress, same Length
if (codeBlock.getStartAddress() != address) {
return false;
}
if (codeBlock.getLength() != numOpcodes) {
return false;
}
}
IMemoryReader codeBlockReader = MemoryReader.getMemoryReader(address, 4);
for (int i = 0; i < numOpcodes; i++) {
int opcode = codeBlockReader.readNext();
if (!nativeCodeSequence.isMatching(i, opcode)) {
return false;
}
}
return true;
}
public NativeCodeSequence getNativeCodeSequence(CodeInstruction codeInstruction, CodeBlock codeBlock) {
int firstOpcode = codeInstruction.getOpcode();
// Fast lookup using the first opcode
if (nativeCodeSequencesByFirstOpcode.containsKey(firstOpcode)) {
for (Iterator<NativeCodeSequence> it = nativeCodeSequencesByFirstOpcode.get(firstOpcode).iterator(); it.hasNext(); ) {
NativeCodeSequence nativeCodeSequence = it.next();
if (isNativeCodeSequence(nativeCodeSequence, codeInstruction, codeBlock)) {
return nativeCodeSequence;
}
}
}
// Slow lookup for sequences having an opcode mask in the first opcode
for (NativeCodeSequence nativeCodeSequence : nativeCodeSequenceWithMaskInFirstOpcode) {
if (isNativeCodeSequence(nativeCodeSequence, codeInstruction, codeBlock)) {
return nativeCodeSequence;
}
}
return null;
}
}