better locking for class intialization

This commit is contained in:
Andreas Gal 2014-07-26 11:42:02 -07:00
Родитель 774d8d3d06
Коммит d394243a8a
5 изменённых файлов: 123 добавлений и 55 удалений

Просмотреть файл

@ -7,6 +7,14 @@ function Context() {
this.frames = [];
}
var Context = (function() {
var pid = 0;
return function() {
this.frames = [];
this.pid = pid++;
}
})();
Context.prototype.current = function() {
var frames = this.frames;
return frames[frames.length - 1];
@ -29,46 +37,45 @@ Context.prototype.popFrame = function() {
return caller;
}
Context.prototype.monitorEnter = function(obj) {
var lock = obj.lock;
if (!lock) {
obj.lock = { thread: this.thread, count: 1, waiters: [] };
return;
}
if (lock.thread === this.thread) {
++lock.count;
return;
}
lock.waiters.push(this);
throw VM.Pause;
}
Context.prototype.monitorLeave = function(obj) {
var lock = obj.lock;
if (lock.thread !== this.thread) {
console.log("WARNING: thread tried to unlock a monitor it didn't own");
return;
}
if (--lock.count > 0) {
return;
}
var waiters = lock.waiters;
obj.lock = null;
for (var n = 0; n < waiters.length; ++n)
window.setZeroTimeout(VM.execute.bind(null, waiters[n]));
}
Context.prototype.pushClassInitFrame = function(classInfo) {
console.log("pushClassInitFrame", classInfo.className, classInfo.initialized);
if (classInfo.initialized)
return;
if (classInfo.superClass)
this.pushClassInitFrame(classInfo.superClass);
classInfo.initialized = true;
var clinit = CLASSES.getMethod(classInfo, "<clinit>", "()V", true, false);
if (!clinit)
if (!clinit) {
classInfo.initialized = true;
return;
this.pushFrame(clinit, 0);
}
// console.log("starting initialization of", classInfo.className, this.thread.toString());
classInfo.thread = this.thread;
var syntheticMethod = {
classInfo: {
constant_pool: [
null,
{ class_index: 2, name_and_type_index: 4 },
{ name_index: 3 },
{ bytes: "java/lang/Class" },
{ name_index: 5, signature_index: 6 },
{ bytes: "invoke_clinit" },
{ bytes: "()V" },
{ class_index: 2, name_and_type_index: 8 },
{ name_index: 9, signature_index: 10 },
{ bytes: "init9" },
{ bytes: "()V" },
],
},
code: [
0x2a, // aload_0
0x59, // dup
0xb7, 0x00, 0x01, // invokespecial <idx=1>
0xb7, 0x00, 0x07, // invokespecial <idx=7>
0xb1, // return
],
exception_table: [],
};
this.current().stack.push(classInfo.getClassObject());
this.pushFrame(syntheticMethod, 1);
}
Context.prototype.backTrace = function() {
@ -192,3 +199,48 @@ Context.prototype.start = function(stopFrame) {
Context.prototype.resume = function() {
this.start(this.stopFrame);
}
Context.prototype.wait = function(obj) {
if (!obj.waiters)
obj.waiters = [];
obj.waiters.push(this);
throw VM.Pause;
}
Context.prototype.notify = function(obj) {
if (!obj.waiters || !obj.waiters.length)
return;
var waiter = obj.waiters.pop();
window.setZeroTimeout(VM.execute.bind(null, waiter));
}
Context.prototype.notifyAll = function(obj) {
while (obj.waiters && obj.waiters.length)
this.notify(obj);
}
Context.prototype.monitorEnter = function(obj) {
var lock = obj.lock;
if (!lock) {
obj.lock = { thread: this.thread, count: 1 };
return;
}
if (lock.thread === this.thread) {
++lock.count;
return;
}
this.wait(obj);
}
Context.prototype.monitorExit = function(obj) {
var lock = obj.lock;
if (lock.thread !== this.thread) {
console.log("WARNING: thread tried to unlock a monitor it didn't own");
return;
}
if (--lock.count > 0) {
return;
}
obj.lock = null;
this.notifyAll(obj);
}

5
jvm.js
Просмотреть файл

@ -44,15 +44,14 @@ JVM.prototype.run = function(className) {
CLASSES.java_lang_Object = CLASSES.loadClass("java/lang/Object");
CLASSES.java_lang_Class = CLASSES.loadClass("java/lang/Class");
CLASSES.java_lang_String = CLASSES.loadClass("java/lang/String");
ctx.pushClassInitFrame(CLASSES.java_lang_Thread = CLASSES.loadClass("java/lang/Thread"));
ctx.execute(caller);
CLASSES.java_lang_Thread = CLASSES.loadClass("java/lang/Thread");
ctx.thread = CLASSES.mainThread = CLASSES.newObject(CLASSES.java_lang_Thread);
caller.stack.push(CLASSES.mainThread);
caller.stack.push(ctx.newString("main"));
ctx.pushFrame(CLASSES.getMethod(CLASSES.java_lang_Thread, "<init>", "(Ljava/lang/String;)V"), 2);
ctx.execute(caller);
caller.stack.push(CLASSES.newArray("[Ljava/lang/String;", 0));
ctx.pushFrame(entryPoint, 1);
ctx.start(caller);

Просмотреть файл

@ -33,8 +33,8 @@ console.log = function() {
//runTest("TestThread");
//runTest("TestRuntime");
runTest("Andreas");
//runTest("Andreas");
//runTest("TestDate");
//runTest("RunAll");
runTest("RunAll");
//runTest("TestArrays");
//runTest("TestByteArrayOutputStream");

Просмотреть файл

@ -72,10 +72,25 @@ Native["java/lang/Object.getClass.()Ljava/lang/Class;"] = function(ctx, stack) {
stack.push(stack.pop().class.getClassObject());
}
Native["java/lang/Class.invoke_clinit.()V"] = function(ctx, stack) {
var classInfo = stack.pop().vmClass;
var clinit = CLASSES.getMethod(classInfo, "<clinit>", "()V", true);
ctx.pushFrame(clinit, 0);
throw VM.Yield;
}
Native["java/lang/Class.init9.()V"] = function(ctx, stack) {
var classObject = stack.pop();
var classInfo = classObject.vmClass;
classInfo.initialized = true;
classInfo.thread = null;
ctx.notifyAll(classObject);
}
Native["java/lang/Class.getName.()Ljava/lang/String;"] = function(ctx, stack) {
var obj = stack.pop();
stack.push(util.cache(obj, "getName", function () {
return ctx.newString(obj.vmClass.className.replace("/", ".", "g"));
var classObject = stack.pop();
stack.push(util.cache(classObject, "getName", function () {
return ctx.newString(classObject.vmClass.className.replace("/", ".", "g"));
}));
}

30
vm.js
Просмотреть файл

@ -15,24 +15,21 @@ VM.execute = function(ctx) {
var stack = frame.stack;
function pushFrame(methodInfo, consumes) {
var lockObject;
if (ACCESS_FLAGS.isSynchronized(methodInfo.access_flags)) {
lockObject = ACCESS_FLAGS.isStatic(methodInfo.access_flags)
? methodInfo.classInfo.getClassObject()
: stack[stack.length - consumes];
ctx.monitorEnter(lockObject);
}
frame = ctx.pushFrame(methodInfo, consumes);
stack = frame.stack;
cp = frame.cp;
if (lockObject)
frame.lockObject = lockObject;
if (ACCESS_FLAGS.isSynchronized(methodInfo.access_flags)) {
frame.lockObject = ACCESS_FLAGS.isStatic(methodInfo.access_flags)
? methodInfo.classInfo.getClassObject()
: frame.getLocal(0);
ctx.monitorEnter(frame.lockObject);
}
return frame;
}
function popFrame(consumes) {
if (frame.lockObject)
ctx.monitorLeave(frame.lockObject);
ctx.monitorExit(frame.lockObject);
var callee = frame;
frame = ctx.popFrame();
stack = frame.stack;
@ -98,6 +95,13 @@ VM.execute = function(ctx) {
function classInitCheck(classInfo, ip) {
if (classInfo.initialized)
return;
if (classInfo.thread) {
// Nothing to do if class initialization is currently in progress on this thread.
if (classInfo.thread === ctx.thread)
return;
ctx.wait(classInfo.getClassObject());
// not reached
}
frame.ip = ip;
ctx.pushClassInitFrame(classInfo);
throw VM.Yield;
@ -105,7 +109,7 @@ VM.execute = function(ctx) {
while (true) {
var op = frame.read8();
console.log(frame.methodInfo.classInfo.className + " " + frame.methodInfo.name + " " + (frame.ip - 1) + " " + OPCODES[op] + " " + stack.join(","));
// console.log("PID" + ctx.pid, frame.methodInfo.classInfo.className + " " + frame.methodInfo.name + " " + (frame.ip - 1) + " " + OPCODES[op] + " " + stack.join(","));
switch (op) {
case 0x00: // nop
break;
@ -165,7 +169,6 @@ VM.execute = function(ctx) {
stack.push(constant.float);
break;
case TAGS.CONSTANT_String:
// console.log(cp[constant.string_index].bytes);
stack.push(ctx.newString(cp[constant.string_index].bytes));
break;
default:
@ -920,7 +923,7 @@ VM.execute = function(ctx) {
ctx.raiseException("java/lang/NullPointerException");
break;
}
ctx.monitorLeave(obj);
ctx.monitorExit(obj);
break;
case 0xc4: // wide
break;
@ -942,7 +945,6 @@ VM.execute = function(ctx) {
var methodInfo = CLASSES.getMethod(classInfo, methodName, signature, isStatic);
var consumes = Signature.parse(methodInfo.signature).IN.slots;
if (isStatic) {
console.log("invokestatic initialized=" + classInfo.initialized + " " + classInfo.className);
classInitCheck(classInfo, startip);
} else {
++consumes;