diff --git a/Makefile b/Makefile
index 4e658d4c..b9d4252d 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,7 @@ BASIC_SRCS=$(shell find . -maxdepth 2 -name "*.ts" -not -path "./build/*")
JIT_SRCS=$(shell find jit -name "*.ts" -not -path "./build/*")
SHUMWAY_SRCS=$(shell find shumway -name "*.ts")
RELEASE ?= 0
+VERSION ?=$(shell date +%s)
all: config-build java jasmin tests j2me shumway aot
@@ -51,7 +52,9 @@ build/shumway.js: $(SHUMWAY_SRCS)
node tools/tsc.js --sourcemap --target ES5 shumway/references.ts --out build/shumway.js
config-build:
- echo "config.release = ${RELEASE};" > config/build.js
+ echo "// generated, build-specific configuration" > config/build.js
+ echo "config.release = ${RELEASE};" >> config/build.js
+ echo "config.version = \"${VERSION}\";" >> config/build.js
tests/tests.jar: tests
tests:
diff --git a/libs/IndexedDB-getAll-shim.js b/libs/IndexedDB-getAll-shim.js
new file mode 100644
index 00000000..364d52d5
--- /dev/null
+++ b/libs/IndexedDB-getAll-shim.js
@@ -0,0 +1,93 @@
+// IndexedDB-getAll-shim v1.1 - https://github.com/jdscheff/IndexedDB-getAll-shim
+//
+// Copyright (c) 2012 Jeremy Scheff
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(function () {
+ "use strict";
+
+ var Event, IDBIndex, IDBObjectStore, IDBRequest, getAll;
+
+ IDBObjectStore = window.IDBObjectStore || window.webkitIDBObjectStore || window.mozIDBObjectStore || window.msIDBObjectStore;
+ IDBIndex = window.IDBIndex || window.webkitIDBIndex || window.mozIDBIndex || window.msIDBIndex;
+
+ if (typeof IDBObjectStore === "undefined" || typeof IDBIndex === "undefined" || (IDBObjectStore.prototype.getAll !== undefined && IDBIndex.prototype.getAll !== undefined)) {
+ return;
+ }
+
+ if (IDBObjectStore.prototype.mozGetAll !== undefined && IDBIndex.prototype.mozGetAll !== undefined) {
+ IDBObjectStore.prototype.getAll = IDBObjectStore.prototype.mozGetAll;
+ IDBIndex.prototype.getAll = IDBIndex.prototype.mozGetAll;
+ return;
+ }
+
+ // https://github.com/axemclion/IndexedDBShim/blob/gh-pages/src/IDBRequest.js
+ IDBRequest = function () {
+ this.onsuccess = null;
+ this.readyState = "pending";
+ };
+ // https://github.com/axemclion/IndexedDBShim/blob/gh-pages/src/Event.js
+ Event = function (type, debug) {
+ return {
+ "type": type,
+ debug: debug,
+ bubbles: false,
+ cancelable: false,
+ eventPhase: 0,
+ timeStamp: new Date()
+ };
+ };
+
+ getAll = function (key) {
+ var request, result;
+
+ key = key !== undefined ? key : null;
+
+ request = new IDBRequest();
+ result = [];
+
+ // this is either an IDBObjectStore or an IDBIndex, depending on the context.
+ this.openCursor(key).onsuccess = function (event) {
+ var cursor, e;
+
+ cursor = event.target.result;
+ if (cursor) {
+ result.push(cursor.value);
+ cursor.continue();
+ } else {
+ if (typeof request.onsuccess === "function") {
+ e = new Event("success");
+ e.target = {
+ readyState: "done",
+ result: result
+ };
+ request.result = result;
+ request.onsuccess(e);
+ }
+ }
+ };
+
+ return request;
+ };
+
+ IDBObjectStore.prototype.getAll = getAll;
+ IDBIndex.prototype.getAll = getAll;
+}());
diff --git a/libs/compiled-method-cache.js b/libs/compiled-method-cache.js
index 3a3e1479..a0d468eb 100644
--- a/libs/compiled-method-cache.js
+++ b/libs/compiled-method-cache.js
@@ -4,7 +4,7 @@
'use strict';
var CompiledMethodCache = (function() {
- var DEBUG = true;
+ var DEBUG = false;
var DATABASE = "CompiledMethodCache";
var VERSION = 1;
var OBJECT_STORE = "methods";
@@ -25,13 +25,13 @@ var CompiledMethodCache = (function() {
};
function restore() {
- return new Promise(function(resolve, reject) {
+ return openDatabase.then(new Promise(function(resolve, reject) {
DEBUG && debug("restore");
+ var then = performance.now();
var transaction = database.transaction(OBJECT_STORE, "readonly");
var objectStore = transaction.objectStore(OBJECT_STORE);
-
- var request = objectStore.mozGetAll();
+ var request = objectStore.getAll();
request.onerror = function() {
console.error("Error restoring: " + request.error.name);
@@ -39,15 +39,39 @@ var CompiledMethodCache = (function() {
};
request.onsuccess = function() {
- for (var i = 0; i < request.result.length; i++) {
+ var count = request.result.length;
+ for (var i = 0; i < count; i++) {
cache.set(request.result[i][KEY_PATH], request.result[i]);
}
+ DEBUG && debug("restore complete: " + count + " methods in " + (performance.now() - then) + "ms");
+ resolve();
+ };
+ }));
+ }
+
+ function clear() {
+ return openDatabase.then(new Promise(function(resolve, reject) {
+ DEBUG && debug("clear");
+
+ // First clear the in-memory cache, in case we've already restored it
+ // from the database.
+ cache.clear();
+
+ var then = performance.now();
+ var transaction = database.transaction(OBJECT_STORE, "readwrite");
+ var objectStore = transaction.objectStore(OBJECT_STORE);
+ var request = objectStore.clear();
+
+ request.onerror = function() {
+ console.error("Error clearing: " + request.error.name);
+ reject(request.error.name);
};
- transaction.oncomplete = function() {
- DEBUG && debug("restore complete");
+ request.onsuccess = function() {
+ DEBUG && debug("clear complete in " + (performance.now() - then) + "ms");
+ resolve();
};
- });
+ }));
}
var openDatabase = new Promise(function(resolve, reject) {
@@ -77,8 +101,19 @@ var CompiledMethodCache = (function() {
request.onsuccess = function() {
DEBUG && debug("open success");
+
database = request.result;
- restore();
+
+ var oldVersion = localStorage.getItem("lastAppVersion");
+ if (config.version === oldVersion) {
+ DEBUG && debug("app version " + config.version + " === " + oldVersion + "; restore");
+ restore().catch(console.error.bind(console));
+ } else {
+ DEBUG && debug("app version " + config.version + " !== " + oldVersion + "; clear");
+ clear().catch(console.error.bind(console));
+ localStorage.setItem("lastAppVersion", config.version);
+ }
+
resolve();
};
});
@@ -104,38 +139,9 @@ var CompiledMethodCache = (function() {
};
request.onsuccess = function() {
- resolve();
- };
-
- transaction.oncomplete = function() {
DEBUG && debug("put " + obj[KEY_PATH] + " complete");
- };
- }));
- }
-
- function clear() {
- DEBUG && debug("clear");
-
- cache.clear();
-
- return openDatabase.then(new Promise(function(resolve, reject) {
- var transaction = database.transaction(OBJECT_STORE, "readwrite");
- var objectStore = transaction.objectStore(OBJECT_STORE);
-
- var request = objectStore.clear();
-
- request.onerror = function() {
- console.error("Error clearing store: " + request.error.name);
- reject(request.error.name);
- };
-
- request.onsuccess = function() {
resolve();
};
-
- transaction.oncomplete = function() {
- DEBUG && debug("clear complete");
- };
}));
}
diff --git a/main.html b/main.html
index d01d1ca9..e82f6b57 100644
--- a/main.html
+++ b/main.html
@@ -30,6 +30,7 @@
+
@@ -117,12 +118,12 @@
-
Import storage:
+
diff --git a/main.js b/main.js
index 65edeab0..dcca3151 100644
--- a/main.js
+++ b/main.js
@@ -175,6 +175,11 @@ function toggle(button) {
var bigBang = 0;
function start() {
+ if (MIDP.manifest["MIDlet-Version"] && MIDP.manifest["MIDlet-Version"] !== localStorage.getItem("lastMidletVersion")) {
+ CompiledMethodCache.clear().catch(console.error.bind(console));
+ localStorage.setItem("lastMidletVersion", MIDP.manifest["MIDlet-Version"]);
+ }
+
J2ME.Context.setWriters(new J2ME.IndentingWriter());
CLASSES.initializeBuiltinClasses();
profiler && profiler.start(2000, false);
@@ -205,9 +210,6 @@ window.onload = function() {
document.getElementById("deleteDatabase").onclick = function() {
indexedDB.deleteDatabase("asyncStorage");
};
- document.getElementById("clearMethodCache").onclick = function() {
- CompiledMethodCache.clear();
- };
document.getElementById("exportstorage").onclick = function() {
fs.exportStore(function(blob) {
saveAs(blob, "fs-" + Date.now() + ".json");
@@ -229,6 +231,9 @@ window.onload = function() {
});
}
};
+ document.getElementById("clearCompiledMethodCache").onclick = function() {
+ CompiledMethodCache.clear().then(function() { console.log("cleared compiled method cache") });
+ };
document.getElementById("trace").onclick = function() {
VM.DEBUG = !VM.DEBUG;
toggle(this);
diff --git a/runtime.ts b/runtime.ts
index b44a907b..02bb95aa 100644
--- a/runtime.ts
+++ b/runtime.ts
@@ -34,6 +34,11 @@ module J2ME {
*/
export var enableOnStackReplacement = true;
+ /**
+ * Turns on caching of JIT-compiled methods.
+ */
+ export var enableCompiledMethodCache = true;
+
/**
* Enables more compact mangled names. This helps reduce code size but may cause naming collisions.
*/
@@ -1072,6 +1077,10 @@ module J2ME {
* callers should be calling that instead of this.
*/
export function linkKlass(classInfo: ClassInfo) {
+ // We shouldn't do any linking if we're not in the runtime phase.
+ if (phase !== ExecutionPhase.Runtime) {
+ return;
+ }
if (classInfo.klass) {
return;
}
@@ -1226,6 +1235,14 @@ module J2ME {
}
function findCompiledMethod(klass: Klass, methodInfo: MethodInfo): Function {
+ if (enableCompiledMethodCache) {
+ var cachedMethod;
+ if (!jsGlobal[methodInfo.mangledClassAndMethodName] && (cachedMethod = CompiledMethodCache.get(methodInfo.implKey))) {
+ cachedMethodCount ++;
+ linkMethod(methodInfo, cachedMethod.source, cachedMethod.referencedClasses);
+ }
+ }
+
return jsGlobal[methodInfo.mangledClassAndMethodName];
}
@@ -1452,12 +1469,13 @@ module J2ME {
return;
}
- var compiledMethod;
-
- if (compiledMethod = CompiledMethodCache.get(methodInfo.implKey)) {
- cachedMethodCount ++;
- jitWriter && jitWriter.writeLn("Getting " + methodInfo.implKey + " from compiled method cache");
- return linkMethod(methodInfo, compiledMethod.source, compiledMethod.referencedClasses);
+ if (enableCompiledMethodCache) {
+ var cachedMethod;
+ if (cachedMethod = CompiledMethodCache.get(methodInfo.implKey)) {
+ cachedMethodCount ++;
+ jitWriter && jitWriter.writeLn("Getting " + methodInfo.implKey + " from compiled method cache");
+ return linkMethod(methodInfo, cachedMethod.source, cachedMethod.referencedClasses);
+ }
}
var mangledClassAndMethodName = methodInfo.mangledClassAndMethodName;
@@ -1465,10 +1483,11 @@ module J2ME {
jitWriter && jitWriter.enter("Compiling: " + methodInfo.implKey + ", currentBytecodeCount: " + methodInfo.bytecodeCount);
var s = performance.now();
- compiledMethodCount ++;
+ var compiledMethod;
enterTimeline("Compiling");
try {
- compiledMethod = baselineCompileMethod(methodInfo, CompilationTarget.Runtime);
+ compiledMethod = baselineCompileMethod(methodInfo, CompilationTarget[enableCompiledMethodCache ? "Static" : "Runtime"]);
+ compiledMethodCount ++;
} catch (e) {
methodInfo.state = MethodState.CannotCompile;
jitWriter && jitWriter.writeLn("Cannot compile: " + methodInfo.implKey + " because of " + e);
@@ -1484,14 +1503,12 @@ module J2ME {
var referencedClasses = compiledMethod.referencedClasses.map(function(v) { return v.className });
- try {
+ if (enableCompiledMethodCache) {
CompiledMethodCache.put({
key: methodInfo.implKey,
source: source,
referencedClasses: referencedClasses,
- });
- } catch(e) {
- jitWriter && jitWriter.writeLn("Cannot cache: " + methodInfo.implKey + " because of " + e);
+ }).catch(stderrWriter.errorLn.bind(stderrWriter));
}
linkMethod(methodInfo, source, referencedClasses);
@@ -1511,6 +1528,8 @@ module J2ME {
* Links up compiled method at runtime.
*/
export function linkMethod(methodInfo: MethodInfo, source: string, referencedClasses: string[]) {
+ jitWriter && jitWriter.writeLn("Link method: " + methodInfo.implKey);
+
enterTimeline("Eval Compiled Code");
// This overwrites the method on the global object.
(1, eval)(source);
diff --git a/tests/automation.js b/tests/automation.js
index a39723a5..7074bb25 100644
--- a/tests/automation.js
+++ b/tests/automation.js
@@ -150,8 +150,9 @@ casper.test.begin("unit tests", 16 + gfxTests.length, function(test) {
.thenOpen("http://localhost:8000/index.html?logConsole=web,page")
.withFrame(0, basicUnitTests);
+ // Run the same unit tests again to test the compiled method cache.
casper
- .thenOpen("http://localhost:8000/index.html?numCalled=1000&logConsole=web,page")
+ .thenOpen("http://localhost:8000/index.html?logConsole=web,page")
.withFrame(0, basicUnitTests);
casper