pluotsorbet/jit/analyze.ts

328 строки
13 KiB
TypeScript

module J2ME {
import assert = Debug.assert;
import unique = ArrayUtilities.unique;
import Bytecodes = Bytecode.Bytecodes;
import BytecodeStream = Bytecode.BytecodeStream;
var yieldWriter = null; // stderrWriter;
export var yieldCounter = null; // new Metrics.Counter(true);
export var yieldGraph = null; // Object.create(null);
export enum YieldReason {
None = 0,
Root = 1,
Synchronized = 2,
MonitorEnterExit = 3,
Virtual = 4,
Cycle = 5,
Yield = 6,
}
/**
* Root set of methods that can yield. Keep this up to date or else the compiler will not generate yield code
* at the right spots.
*/
export var yieldMap = {
"com/sun/cdc/io/j2me/file/DefaultFileHandler.openForRead.()V": YieldReason.Root,
"com/sun/cdc/io/j2me/file/DefaultFileHandler.openForWrite.()V": YieldReason.Root,
"java/lang/Thread.sleep.(J)V": YieldReason.Root,
"com/sun/cldc/isolate/Isolate.waitStatus.(I)V": YieldReason.Root,
"com/sun/j2me/location/PlatformLocationProvider.waitForNewLocation.(IJ)Z": YieldReason.Root,
"com/sun/javame/sensor/NativeChannel.doMeasureData.(II)[B": YieldReason.Root,
"com/sun/midp/links/LinkPortal.getLinkCount0.()I": YieldReason.Root,
"com/sun/midp/links/Link.receive0.(Lcom/sun/midp/links/LinkMessage;Lcom/sun/midp/links/Link;)V": YieldReason.Root,
"com/nokia/mid/impl/jms/core/Launcher.handleContent.(Ljava/lang/String;)V": YieldReason.Root,
"com/sun/midp/util/isolate/InterIsolateMutex.lock0.(I)V": YieldReason.Root,
"com/sun/midp/events/NativeEventMonitor.waitForNativeEvent.(Lcom/sun/midp/events/NativeEvent;)I": YieldReason.Root,
"com/sun/midp/io/j2me/push/ConnectionRegistry.poll0.(J)I": YieldReason.Root,
"com/sun/midp/rms/RecordStoreFile.openRecordStoreFile.(Ljava/lang/String;Ljava/lang/String;I)I": YieldReason.Root,
"com/sun/midp/io/j2me/storage/RandomAccessStream.open.(Ljava/lang/String;I)I": YieldReason.Root,
"javax/microedition/lcdui/ImageDataFactory.createImmutableImageDecodeImage.(Ljavax/microedition/lcdui/ImageData;[BII)V": YieldReason.Root,
"com/nokia/mid/ui/TextEditorThread.getNextDirtyEditor.()Lcom/nokia/mid/ui/TextEditor;": YieldReason.Root,
"com/nokia/mid/ui/TextEditor.setFocus.(Z)V": YieldReason.Root,
"com/nokia/mid/ui/VKVisibilityNotificationRunnable.sleepUntilVKVisibilityChange.()Z": YieldReason.Root,
"com/nokia/mid/s40/bg/BGUtils.waitUserInteraction.()V": YieldReason.Root,
"org/mozilla/io/LocalMsgConnection.init.(Ljava/lang/String;)V": YieldReason.Root,
"org/mozilla/io/LocalMsgConnection.receiveData.([B)I": YieldReason.Root,
"org/mozilla/io/LocalMsgConnection.waitConnection.()V": YieldReason.Root,
"com/sun/mmedia/PlayerImpl.nRealize.(ILjava/lang/String;)Z": YieldReason.Root,
"com/sun/mmedia/DirectRecord.nPause.(I)I": YieldReason.Root,
"com/sun/mmedia/DirectRecord.nStop.(I)I": YieldReason.Root,
"com/sun/mmedia/DirectRecord.nClose.(I)I": YieldReason.Root,
"com/sun/mmedia/DirectRecord.nStart.(I)I": YieldReason.Root,
"com/sun/midp/io/j2me/socket/Protocol.open0.([BI)V": YieldReason.Root,
"com/sun/midp/io/j2me/socket/Protocol.read0.([BII)I": YieldReason.Root,
"com/sun/midp/io/j2me/socket/Protocol.write0.([BII)I": YieldReason.Root,
"com/sun/midp/io/j2me/socket/Protocol.close0.()V": YieldReason.Root,
"com/sun/midp/io/j2me/sms/Protocol.receive0.(IIILcom/sun/midp/io/j2me/sms/Protocol$SMSPacket;)I": YieldReason.Root,
"com/sun/midp/io/j2me/sms/Protocol.send0.(IILjava/lang/String;II[B)I": YieldReason.Root,
"com/sun/j2me/pim/PIMProxy.getNextItemDescription0.(I[I)Z": YieldReason.Root,
"java/lang/Object.wait.(J)V": YieldReason.Root,
"java/lang/Class.invoke_clinit.()V": YieldReason.Root,
"java/lang/Class.newInstance.()Ljava/lang/Object;": YieldReason.Root,
"java/lang/Thread.yield.()V": YieldReason.Root,
"java/lang/Thread.start0.()V": YieldReason.Root,
"java/lang/Class.forName0.(Ljava/lang/String;)V": YieldReason.Root,
"java/lang/Class.newInstance1.(Ljava/lang/Object;)V": YieldReason.Root,
// Test Files:
"gnu/testlet/vm/NativeTest.throwExceptionAfterPause.()V": YieldReason.Root,
"gnu/testlet/vm/NativeTest.returnAfterPause.()I": YieldReason.Root,
"gnu/testlet/vm/NativeTest.dumbPipe.()Z": YieldReason.Root,
"gnu/testlet/TestHarness.getNumDifferingPixels.(Ljava/lang/String;)I": YieldReason.Root,
};
export var yieldVirtualMap = {
// These can technically yield but are worth the risk.
// XXX Determine the current status of this item.
// "java/lang/Object.equals.(Ljava/lang/Object;)Z": YieldReason.None
};
export function isFinalClass(classInfo: ClassInfo): boolean {
var result = classInfo.isFinal;
if (!result) {
result = classInfo.subClasses.length === 0;
}
// console.log(classInfo.className + " is final class " + result);
return result;
}
export function isFinalMethod(methodInfo: MethodInfo): boolean {
if (isFinalClass(methodInfo.classInfo)) {
return true;
}
// XXX Determine whether we can start using the code in this function.
return false;
var result = methodInfo.isFinal;
if (!result) {
var classInfo = methodInfo.classInfo;
var allSubClasses = classInfo.allSubClasses;
result = true;
for (var i = 0; i < allSubClasses.length; i++) {
var subClassMethods = allSubClasses[i].getMethods();
for (var j = 0; j < subClassMethods.length; j++) {
var subClassMethodInfo = subClassMethods[j];
if (methodInfo.name === subClassMethodInfo.name &&
methodInfo.signature === subClassMethodInfo.signature) {
result = false;
break;
}
}
}
}
return result;
}
export function gatherCallees(callees: MethodInfo [], classInfo: ClassInfo, methodInfo: MethodInfo) {
var methods = classInfo.getMethods();
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
if (method.name === methodInfo.name && method.signature === methodInfo.signature) {
callees.push(method);
}
}
var subClasses = classInfo.subClasses;
for (var i = 0; i < subClasses.length; i++) {
var subClass = subClasses[i];
gatherCallees(callees, subClass, methodInfo);
}
}
export function isStaticallyBound(op: Bytecodes, methodInfo: MethodInfo): boolean {
// INVOKESPECIAL and INVOKESTATIC are always statically bound.
if (op === Bytecodes.INVOKESPECIAL || op === Bytecodes.INVOKESTATIC) {
return true;
}
// INVOKEVIRTUAL is only statically bound if its class is final.
if (op === Bytecodes.INVOKEVIRTUAL && isFinalMethod(methodInfo)) {
return true;
}
return false;
}
// Used to prevent cycles.
var checkingForCanYield = Object.create(null);
function addDependency(callee: MethodInfo, caller: MethodInfo, reason: YieldReason) {
if (!yieldGraph) {
return;
}
if (!yieldGraph[callee.implKey]) {
yieldGraph[callee.implKey] = Object.create(null);
}
var node = yieldGraph[callee.implKey];
node[caller.implKey] = reason;
}
function countDescendents(root) {
var visited = Object.create(null);
var visiting = Object.create(null);
var w = new IndentingWriter();
function visit(name) {
if (!yieldGraph[name]) {
return 0;
}
if (visiting[name]) {
return 0;
}
var n = 0;
visiting[name] = true;
for (var k in yieldGraph[name]) {
n ++;
n += visit(k);
}
visiting[name] = false;
return n;
}
return visit(root);
}
export function traceYieldGraph(writer: IndentingWriter) {
writer.writeLn(JSON.stringify(yieldGraph, null, 2));
var pairs = [];
for (var k in yieldGraph) {
pairs.push([k, countDescendents(k)]);
}
pairs.sort(function (a, b) {
return b[1] - a[1];
});
for (var i = 0; i < pairs.length; i++) {
var p = pairs[i];
writer.writeLn(pairs[i][0] + ": " + pairs[i][1]);
}
}
export function canStaticInitializerYield(classInfo: ClassInfo): YieldReason {
var result = YieldReason.None;
while (classInfo) {
var staticInitializer = classInfo.staticInitializer;
classInfo = classInfo.superClass;
if (!staticInitializer) {
continue;
}
result = canYield(staticInitializer);
if (result !== YieldReason.None) {
return result;
}
}
return result;
}
export function canYield(methodInfo: MethodInfo): YieldReason {
yieldWriter && yieldWriter.enter("> " + methodInfo.implKey);
if (yieldMap[methodInfo.implKey] !== undefined) {
yieldWriter && yieldWriter.leave("< " + methodInfo.implKey + " " + YieldReason[yieldMap[methodInfo.implKey]] + " cached.");
return yieldMap[methodInfo.implKey];
}
if (methodInfo.isSynchronized) {
yieldCounter && yieldCounter.count("Method: " + methodInfo.implKey + " yields because it is synchronized.");
yieldWriter && yieldWriter.leave("< " + methodInfo.implKey + " " + YieldReason[YieldReason.Synchronized]);
return yieldMap[methodInfo.implKey] = YieldReason.Synchronized;
}
if (checkingForCanYield[methodInfo.implKey]) {
yieldWriter && yieldWriter.leave("< " + methodInfo.implKey + " " + YieldReason[YieldReason.Cycle]);
return YieldReason.Cycle;
}
if (!methodInfo.code) {
assert (methodInfo.isNative || methodInfo.isAbstract);
yieldWriter && yieldWriter.leave("< " + methodInfo.implKey + " Abstract");
return yieldMap[methodInfo.implKey] = YieldReason.None;
}
checkingForCanYield[methodInfo.implKey] = true;
var constantPool = methodInfo.classInfo.constantPool;
try {
var result = YieldReason.None;
var stream = new BytecodeStream(methodInfo.code);
stream.setBCI(0);
while (stream.currentBCI < methodInfo.code.length) {
var op: Bytecodes = stream.currentBC();
switch (op) {
case Bytecodes.NEW:
case Bytecodes.GETSTATIC:
case Bytecodes.PUTSTATIC:
var cpi = stream.readCPI();
var fieldInfo = constantPool.resolveField(cpi, true);
var classInfo = fieldInfo.classInfo;
result = canStaticInitializerYield(classInfo);
break;
case Bytecodes.MONITORENTER:
case Bytecodes.MONITOREXIT:
result = YieldReason.MonitorEnterExit;
yieldCounter && yieldCounter.count("Method: " + methodInfo.implKey + " yields because it has monitor enter/exit.");
break;
case Bytecodes.INVOKEINTERFACE:
result = YieldReason.Virtual;
if (result) {
yieldCounter && yieldCounter.count("Method: " + methodInfo.implKey + " yields because it has an invoke interface.");
}
break;
case Bytecodes.INVOKEVIRTUAL:
case Bytecodes.RESOLVED_INVOKEVIRTUAL:
case Bytecodes.INVOKESPECIAL:
case Bytecodes.INVOKESTATIC:
var cpi = stream.readCPI();
var callee = constantPool.resolveMethod(cpi, op === Bytecodes.INVOKESTATIC);
if (op !== Bytecodes.INVOKESTATIC) {
if (yieldVirtualMap[methodInfo.implKey] === YieldReason.None) {
result = YieldReason.None;
break;
}
}
if (op === Bytecodes.INVOKESTATIC) {
result = canStaticInitializerYield(methodInfo.classInfo);
if (result !== YieldReason.None) {
break;
}
}
if (!isStaticallyBound(op, callee)) {
var callees = [];
result = YieldReason.Virtual;
if (false) { // Checking all possible callees, disabled for now until fully tested.
result = YieldReason.None;
gatherCallees(callees, callee.classInfo, callee);
yieldWriter && yieldWriter.writeLn("Gather: " + callee.implKey + " " + callees.map(x => x.implKey).join(", "));
for (var i = 0; i < callees.length; i++) {
if (canYield(callees[i])) {
yieldWriter && yieldWriter.writeLn("Gathered Method: " + callees[i].implKey + " yields.");
result = YieldReason.Virtual;
break;
}
}
}
if (result !== YieldReason.None) {
yieldCounter && yieldCounter.count("Method: " + methodInfo.implKey + " yields because callee: " + callee.implKey + " is not statically bound.");
addDependency(callee, methodInfo, YieldReason.Virtual);
}
break;
}
result = canYield(callee);
if (result) {
yieldCounter && yieldCounter.count("Callee: " + callee.implKey + " yields.");
yieldCounter && yieldCounter.count("Method: " + methodInfo.implKey + " yields because callee: " + callee.implKey + " can yield.");
addDependency(callee, methodInfo, YieldReason.Yield);
}
break;
}
if (result) {
break;
}
stream.next();
}
} catch (e) {
result = YieldReason.Cycle;
// stderrWriter.writeLn("ERROR: " + methodInfo.implKey + " Cycle");
// stderrWriter.writeLn(e);
// stderrWriter.writeLns(e.stack);
}
checkingForCanYield[methodInfo.implKey] = false;
yieldWriter && yieldWriter.leave("< " + methodInfo.implKey + " " + YieldReason[result]);
return yieldMap[methodInfo.implKey] = result;
}
}