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