From c6bdc9d365b9df693eb8d7d394d8db30bd7b3107 Mon Sep 17 00:00:00 2001 From: Michael Bebenita Date: Mon, 10 Aug 2015 14:47:50 -0700 Subject: [PATCH 01/11] Make thresholds configurable. --- int.ts | 8 ++++---- jsshell.js | 13 +++++++++++++ vm/runtime.ts | 8 ++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/int.ts b/int.ts index b8a56b59..4a7e6510 100644 --- a/int.ts +++ b/int.ts @@ -618,7 +618,7 @@ module J2ME { var method = function fastInterpreterFrameAdapter() { var calleeStats = methodInfo.stats; calleeStats.interpreterCallCount++; - if (calleeStats.interpreterCallCount + calleeStats.backwardsBranchCount > ConfigConstants.InvokeThreshold) { + if (calleeStats.interpreterCallCount + calleeStats.backwardsBranchCount > ConfigThresholds.InvokeThreshold) { compileAndLinkMethod(methodInfo); if(methodInfo.state === MethodState.Compiled) { return methodInfo.fn.apply(null, arguments); @@ -729,7 +729,7 @@ module J2ME { var mi = frame.methodInfo; release || assert(mi, "Must have method info."); mi.stats.interpreterCallCount++; - if (mi.state === MethodState.Cold && mi.stats.interpreterCallCount + mi.stats.backwardsBranchCount > ConfigConstants.InvokeThreshold) { + if (mi.state === MethodState.Cold && mi.stats.interpreterCallCount + mi.stats.backwardsBranchCount > ConfigThresholds.InvokeThreshold) { compileAndLinkMethod(mi); // TODO call the compiled method. } @@ -1539,7 +1539,7 @@ module J2ME { jumpOffset = ((code[pc++] << 8 | code[pc++]) << 16 >> 16); if (jumpOffset < 0) { mi.stats.backwardsBranchCount++; - if (mi.state === MethodState.Cold && mi.stats.interpreterCallCount + mi.stats.backwardsBranchCount > ConfigConstants.BackwardBranchThreshold) { + if (mi.state === MethodState.Cold && mi.stats.interpreterCallCount + mi.stats.backwardsBranchCount > ConfigThresholds.BackwardBranchThreshold) { compileAndLinkMethod(mi); } if (enableOnStackReplacement && mi.state === MethodState.Compiled) { @@ -2161,7 +2161,7 @@ module J2ME { var calleeStats = calleeTargetMethodInfo.stats; calleeStats.interpreterCallCount++; if (callMethod === false && calleeTargetMethodInfo.state === MethodState.Cold) { - if (calleeStats.interpreterCallCount + calleeStats.backwardsBranchCount > ConfigConstants.InvokeThreshold) { + if (calleeStats.interpreterCallCount + calleeStats.backwardsBranchCount > ConfigThresholds.InvokeThreshold) { compileAndLinkMethod(calleeTargetMethodInfo); callMethod = calleeTargetMethodInfo.state === MethodState.Compiled; } diff --git a/jsshell.js b/jsshell.js index 4555993b..616df16c 100755 --- a/jsshell.js +++ b/jsshell.js @@ -77,6 +77,16 @@ var options = { short: "m", value: -1, type: "number" + }, + "backwardBranchThreshold": { + short: "bbt", + value: 1, + type: "number" + }, + "invokeThreshold": { + short: "it", + value: 1, + type: "number" } }; @@ -242,6 +252,9 @@ try { J2ME.enableRuntimeCompilation = false; J2ME.maxCompiledMethodCount = options.maxCompiledMethodCount.value; + J2ME.ConfigThresholds.InvokeThreshold = options.invokeThreshold.value; + J2ME.ConfigThresholds.BackwardBranchThreshold = options.backwardBranchThreshold.value; + start = dateNow(); var runtime = jvm.startIsolate0(files[0], config.args); diff --git a/vm/runtime.ts b/vm/runtime.ts index 4e14d52a..780a077a 100644 --- a/vm/runtime.ts +++ b/vm/runtime.ts @@ -1051,7 +1051,7 @@ module J2ME { } // Don't compile if we've compiled too many methods. - if (maxCompiledMethodCount > 0 && compiledMethodCount >= maxCompiledMethodCount) { + if (maxCompiledMethodCount >= 0 && compiledMethodCount >= maxCompiledMethodCount) { return; } // Don't compile methods that are too large. @@ -1483,9 +1483,9 @@ module J2ME { } } - export enum ConfigConstants { - InvokeThreshold = 1, - BackwardBranchThreshold = 1 + export class ConfigThresholds { + static InvokeThreshold = 10; + static BackwardBranchThreshold = 10; } export enum Constants { From effce3065bc7a97d9d33abd0088f7fa729fe71db Mon Sep 17 00:00:00 2001 From: Michael Bebenita Date: Mon, 10 Aug 2015 15:14:38 -0700 Subject: [PATCH 02/11] Add OSR option. --- jsshell.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jsshell.js b/jsshell.js index 616df16c..1ce007cf 100755 --- a/jsshell.js +++ b/jsshell.js @@ -55,6 +55,9 @@ function parseArguments(options, tokens) { case "string": options[name].value = value; break; + case "boolean": + options[name].value = value == "true" || value == "yes"; + break; } } else { print("Illegal option: " + name); @@ -87,6 +90,11 @@ var options = { short: "it", value: 1, type: "number" + }, + "enableOnStackReplacement": { + short: "osr", + value: true, + type: "boolean" } }; @@ -254,6 +262,7 @@ try { J2ME.ConfigThresholds.InvokeThreshold = options.invokeThreshold.value; J2ME.ConfigThresholds.BackwardBranchThreshold = options.backwardBranchThreshold.value; + J2ME.enableOnStackReplacement = options.enableOnStackReplacement.value; start = dateNow(); var runtime = jvm.startIsolate0(files[0], config.args); From 325b14fb8de9f2c25bcb4f4f68b5b812143d19df Mon Sep 17 00:00:00 2001 From: Michael Bebenita Date: Mon, 10 Aug 2015 15:16:22 -0700 Subject: [PATCH 03/11] Add a GC_NONE mode where we do bump allocation. --- vm/native/native.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/vm/native/native.cpp b/vm/native/native.cpp index 8b794120..ba696028 100644 --- a/vm/native/native.cpp +++ b/vm/native/native.cpp @@ -9,6 +9,11 @@ #include #include +#define GC_NONE 1 +#define GC_NONE_HEAP_SIZE (128 * 1024 * 1024) + +uint32_t * heap, * head; + // Formatting: Printing longs. // printf("L: %" PRId64 ", R: %" PRId64, *l, *r); @@ -65,11 +70,28 @@ extern "C" { } uintptr_t gcMalloc(int32_t size) { + #ifdef GC_NONE + size = (size + 3) & ~0x03; + uint32_t * curr = head; + uint32_t * p = head; + head += size; + if (head > heap + GC_NONE_HEAP_SIZE) { + printf("Out of memory, max: %d", GC_NONE_HEAP_SIZE); + return 0; + } + while (curr < head) *curr++ = 0; + return (uintptr_t)p; + #else return (uintptr_t)GC_MALLOC_UNCOLLECTABLE(size); + #endif } uintptr_t gcMallocAtomic(int32_t size) { + #ifdef GC_NONE + return gcMalloc(size); + #else return (uintptr_t)GC_MALLOC_UNCOLLECTABLE(size); + #endif } void gcRegisterDisappearingLink(uintptr_t p, uintptr_t objAddr) { @@ -97,5 +119,8 @@ extern "C" { } int main() { + #ifdef GC_NONE + heap = head = (uint32_t *)malloc(GC_NONE_HEAP_SIZE); + #endif GC_INIT(); } From 4c0811d37d667cff3e1b690123d8a0910cad789a Mon Sep 17 00:00:00 2001 From: Michael Bebenita Date: Mon, 10 Aug 2015 19:01:27 -0700 Subject: [PATCH 04/11] Workaround JS engine bug 1193112 that triggers too many bailouts. --- int.ts | 4 ++-- vm/runtime.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/int.ts b/int.ts index 4a7e6510..5601f0cd 100644 --- a/int.ts +++ b/int.ts @@ -1330,7 +1330,7 @@ module J2ME { case Bytecodes.FCMPG: var FCMP_fb = f32[--sp]; var FCMP_fa = f32[--sp]; - if (isNaN(FCMP_fa) || isNaN(FCMP_fb)) { + if (FCMP_fa !== FCMP_fa || FCMP_fb !== FCMP_fb) { i32[sp++] = op === Bytecodes.FCMPL ? -1 : 1; } else if (FCMP_fa > FCMP_fb) { i32[sp++] = 1; @@ -1349,7 +1349,7 @@ module J2ME { aliasedI32[1] = i32[sp - 3]; var DCMP_fa = aliasedF64[0]; sp -= 4; - if (isNaN(DCMP_fa) || isNaN(DCMP_fb)) { + if (DCMP_fa !== DCMP_fa || DCMP_fb !== DCMP_fb) { i32[sp++] = op === Bytecodes.DCMPL ? -1 : 1; } else if (DCMP_fa > DCMP_fb) { i32[sp++] = 1; diff --git a/vm/runtime.ts b/vm/runtime.ts index 780a077a..c0e65487 100644 --- a/vm/runtime.ts +++ b/vm/runtime.ts @@ -1678,7 +1678,7 @@ module J2ME { export function fcmp(a: number, b: number, isLessThan: boolean): number { var x = (aliasedI32[0] = a, aliasedF32[0]); var y = (aliasedI32[0] = b, aliasedF32[0]); - if (isNaN(x) || isNaN(y)) { + if (x !== x || y !== y) { return isLessThan ? -1 : 1; } else if (x > y) { return 1; @@ -1697,7 +1697,7 @@ module J2ME { export function dcmp(al: number, ah: number, bl: number, bh: number, isLessThan: boolean) { var x = (aliasedI32[0] = al, aliasedI32[1] = ah, aliasedF64[0]); var y = (aliasedI32[0] = bl, aliasedI32[1] = bh, aliasedF64[0]); - if (isNaN(x) || isNaN(y)) { + if (x !== x || y !== y) { return isLessThan ? -1 : 1; } else if (x > y) { return 1; From 037c27ce73ec008ab3db3f2b70d20ae5892390f7 Mon Sep 17 00:00:00 2001 From: Michael Bebenita Date: Mon, 10 Aug 2015 19:38:13 -0700 Subject: [PATCH 05/11] Add more benchmarks. --- bench/Arithmetic.java | 73 ++++++++++++++++++++++++++ bench/Fields.java | 65 +++++++++++++++++++++++ bench/Invoke.java | 102 +++++++++++++++++++++++++++++++++++++ bench/InvokeInterface.java | 2 +- bench/InvokeStatic.java | 2 +- bench/InvokeVirtual.java | 2 +- 6 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 bench/Arithmetic.java create mode 100644 bench/Fields.java create mode 100644 bench/Invoke.java diff --git a/bench/Arithmetic.java b/bench/Arithmetic.java new file mode 100644 index 00000000..37839ed7 --- /dev/null +++ b/bench/Arithmetic.java @@ -0,0 +1,73 @@ +package benchmark; +import com.sun.cldchi.jvm.JVM; + +class Arithmetic { + public static void f(int a) {} + public static void f(int a, int b) {} + public static void f(int a, int b, int c) {} + + public static void f(long a) {} + public static void f(long a, long b) {} + public static void f(long a, long b, long c) {} + + public static void f(float a) {} + public static void f(float a, float b) {} + public static void f(float a, float b, float c) {} + + public static void f(double a) {} + public static void f(double a, double b) {} + public static void f(double a, double b, double c) {} + + public static void main(String[] args) { + int i = 0; + long l = 0; + float f = 123; + double d = 0; + + long start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + i += 1; + i >>= 2; + i <<= 3; + i &= 4; + i |= 5; + } + + System.out.println("int: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + l += 1; + l >>= 2; + l <<= 3; + l &= 4; + l |= 5; + } + + System.out.println("long: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + f += 1; + f -= 1; + f *= 1; + f /= 1; + f %= 1; + f = -f; + f = f < f ? f : f; + } + + System.out.println("float: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + d += 1; + d -= 1; + d *= 1; + d /= 1; + d %= 1; + d = -d; + d = d < d ? d : d; + } + + System.out.println("double: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + } +} diff --git a/bench/Fields.java b/bench/Fields.java new file mode 100644 index 00000000..4af1b814 --- /dev/null +++ b/bench/Fields.java @@ -0,0 +1,65 @@ +package benchmark; +import com.sun.cldchi.jvm.JVM; + +class Fields { + public static int i; + public static long l; + public static float f; + public static double d; + + public int I; + public long L; + public float F; + public double D; + + public static void main(String[] args) { + + long start = JVM.monotonicTimeMillis(); + + // Static Fields + + for (int k = 0; k < 1000000; k++) { + i = i; i = i; i = i; i = i; i = i; + } + System.out.println("static int: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + l = l; l = l; l = l; l = l; l = l; + } + System.out.println("static long: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + f = f; f = f; f = f; f = f; f = f; + } + System.out.println("static float: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + d = d; d = d; d = d; d = d; d = d; + } + System.out.println("static double: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + // Instance Fields + + Fields x = new Fields(); + + for (int k = 0; k < 1000000; k++) { + x.I = x.I; x.I = x.I; x.I = x.I; x.I = x.I; x.I = x.I; + } + System.out.println("int: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + x.L = x.L; x.L = x.L; x.L = x.L; x.L = x.L; x.L = x.L; + } + System.out.println("long: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + x.F = x.F; x.F = x.F; x.F = x.F; x.F = x.F; x.F = x.F; + } + System.out.println("float: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + x.D = x.D; x.D = x.D; x.D = x.D; x.D = x.D; x.D = x.D; + } + System.out.println("double: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + } +} diff --git a/bench/Invoke.java b/bench/Invoke.java new file mode 100644 index 00000000..5017822c --- /dev/null +++ b/bench/Invoke.java @@ -0,0 +1,102 @@ +package benchmark; +import com.sun.cldchi.jvm.JVM; + +class Invoke { + public static void f(int a) {} + public static void f(int a, int b) {} + public static void f(int a, int b, int c) {} + public static void f(int a, int b, int c, int d) {} + public static void f(int a, int b, int c, int d, int e) {} + public static void f(int a, int b, int c, int d, int e, int f) {} + public static void f(int a, int b, int c, int d, int e, int f, int g) {} + public static void f(int a, int b, int c, int d, int e, int f, int g, int h) {} + + public static void f(long a) {} + public static void f(long a, long b) {} + public static void f(long a, long b, long c) {} + public static void f(long a, long b, long c, long d) {} + public static void f(long a, long b, long c, long d, long e) {} + public static void f(long a, long b, long c, long d, long e, long f) {} + public static void f(long a, long b, long c, long d, long e, long f, long g) {} + public static void f(long a, long b, long c, long d, long e, long f, long g, long h) {} + + public static void f(float a) {} + public static void f(float a, float b) {} + public static void f(float a, float b, float c) {} + public static void f(float a, float b, float c, float d) {} + public static void f(float a, float b, float c, float d, float e) {} + public static void f(float a, float b, float c, float d, float e, float f) {} + public static void f(float a, float b, float c, float d, float e, float f, float g) {} + public static void f(float a, float b, float c, float d, float e, float f, float g, float h) {} + + public static void f(double a) {} + public static void f(double a, double b) {} + public static void f(double a, double b, double c) {} + public static void f(double a, double b, double c, double d) {} + public static void f(double a, double b, double c, double d, double e) {} + public static void f(double a, double b, double c, double d, double e, double f) {} + public static void f(double a, double b, double c, double d, double e, double f, double g) {} + public static void f(double a, double b, double c, double d, double e, double f, double g, double h) {} + + + public static void main(String[] args) { + int i = 0; + long l = 0; + float f = 0; + double d = 0; + + long start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + f(i); + f(i, i); + f(i, i, i); + f(i, i, i, i); + f(i, i, i, i, i); + f(i, i, i, i, i, i); + f(i, i, i, i, i, i, i); + f(i, i, i, i, i, i, i, i); + } + + System.out.println("int: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + f(l); + f(l, l); + f(l, l, l); + f(l, l, l, l); + f(l, l, l, l, l); + f(l, l, l, l, l, l); + f(l, l, l, l, l, l, l); + f(l, l, l, l, l, l, l, l); + } + + System.out.println("long: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + f(f); + f(f, f); + f(f, f, f); + f(f, f, f, f); + f(f, f, f, f, f); + f(f, f, f, f, f, f); + f(f, f, f, f, f, f, f); + f(f, f, f, f, f, f, f, f); + } + + System.out.println("float: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + + for (int k = 0; k < 1000000; k++) { + f(d); + f(d, d); + f(d, d, d); + f(d, d, d, d); + f(d, d, d, d, d); + f(d, d, d, d, d, d); + f(d, d, d, d, d, d, d); + f(d, d, d, d, d, d, d, d); + } + + System.out.println("double: " + (JVM.monotonicTimeMillis() - start)); start = JVM.monotonicTimeMillis(); + } +} diff --git a/bench/InvokeInterface.java b/bench/InvokeInterface.java index 2b1b6c2c..cbfb5874 100644 --- a/bench/InvokeInterface.java +++ b/bench/InvokeInterface.java @@ -13,7 +13,7 @@ class IFaceImpl implements IFace { class InvokeInterface { public static void main(String[] args) { IFace foo = new IFaceImpl(); - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 10000000; i++) { foo.method(); } } diff --git a/bench/InvokeStatic.java b/bench/InvokeStatic.java index f72802b5..10b92fcb 100644 --- a/bench/InvokeStatic.java +++ b/bench/InvokeStatic.java @@ -6,7 +6,7 @@ class InvokeStatic { } public static void main(String[] args) { - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 10000000; i++) { staticus(); } } diff --git a/bench/InvokeVirtual.java b/bench/InvokeVirtual.java index 7078343f..e0b006df 100644 --- a/bench/InvokeVirtual.java +++ b/bench/InvokeVirtual.java @@ -15,7 +15,7 @@ class NextClass extends BaseClass { class InvokeVirtual { public static void main(String[] args) { NextClass foo = new NextClass(); - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 10000000; i++) { foo.method(); } } From 26242970ab281de4b751a53a6d21b7f35dbc1c53 Mon Sep 17 00:00:00 2001 From: Michael Bebenita Date: Tue, 11 Aug 2015 01:18:08 -0700 Subject: [PATCH 06/11] Print some stats when jsshell exits. --- jsshell.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jsshell.js b/jsshell.js index 1ce007cf..9893dc16 100755 --- a/jsshell.js +++ b/jsshell.js @@ -272,8 +272,12 @@ try { return true; }); - print("Time: " + (dateNow() - start).toFixed(4) + " ms"); - J2ME.bytecodeCount && print("Bytecodes: " + J2ME.bytecodeCount); + print("-------------------------------------------------------"); + print("Total Time: " + (dateNow() - start).toFixed(4) + " ms"); + print("bytecodeCount: " + J2ME.bytecodeCount); + print("compiledMethodCount: " + J2ME.compiledMethodCount); + print("onStackReplacementCount: " + J2ME.onStackReplacementCount); + print("-------------------------------------------------------"); J2ME.interpreterCounter.traceSorted(new J2ME.IndentingWriter(false, function (x) { print(x); })); From 429b794930a2302d6a96ebe8ee74116ff9a4db09 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 11 Aug 2015 15:59:55 -0700 Subject: [PATCH 07/11] Use constants for thread data. --- vm/context.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/vm/context.ts b/vm/context.ts index af92fccd..7159338d 100644 --- a/vm/context.ts +++ b/vm/context.ts @@ -148,6 +148,12 @@ module J2ME { } + enum ThreadDataLayout { + AddressOffset = 0, + StackTopOffset = 1, + Size = 2 + } + export class Context { private static _nextId: number = 0; private static _colors = [ @@ -191,17 +197,17 @@ module J2ME { } setUncollectable(this.nativeThread.tp); - this.threadData = new Int32Array(ASM.buffer, ASM._gcMallocUncollectable(8), 2); - this.threadData[1] = this.nativeThread.tp; + this.threadData = new Int32Array(ASM.buffer, ASM._gcMallocUncollectable(ThreadDataLayout.Size << 2), ThreadDataLayout.Size); + this.threadData[ThreadDataLayout.StackTopOffset] = this.nativeThread.tp; unsetUncollectable(this.nativeThread.tp); } public set threadAddress(addr: number) { - this.threadData[0] = addr; + this.threadData[ThreadDataLayout.AddressOffset] = addr; } public get threadAddress() { - return this.threadData[0]; + return this.threadData[ThreadDataLayout.AddressOffset]; } public static color(id) { From 095c8cbd318e043ec992100048c418fd8f5362f1 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 11 Aug 2015 16:06:25 -0700 Subject: [PATCH 08/11] Re-enable GC when there are no native frames on the call stack. --- int.ts | 53 +++++++++++++++++++++++++++++++------------- jit/analyze.ts | 1 + jit/baseline.ts | 22 +++++++++++++----- native.js.in | 9 +++++++- vm/context.ts | 7 +++--- vm/native/native.cpp | 14 ++++++++++-- vm/runtime.ts | 49 +++++++++++++++++++++++++++------------- 7 files changed, 112 insertions(+), 43 deletions(-) diff --git a/int.ts b/int.ts index 8edc9a0e..80d03992 100644 --- a/int.ts +++ b/int.ts @@ -325,6 +325,8 @@ module J2ME { */ pc: number + nativeFrameCount: number; + /** * Context associated with this thread. */ @@ -353,6 +355,7 @@ module J2ME { this.ctx = ctx; this.unwoundNativeFrames = []; this.pendingNativeFrames = []; + this.nativeFrameCount = 0; release || threadWriter && threadWriter.writeLn("creatingThread: tp: " + toHEX(this.tp) + " " + toHEX(this.tp + Constants.MAX_STACK_SIZE)); } @@ -374,6 +377,9 @@ module J2ME { } pushMarkerFrame(frameType: FrameType) { + if (frameType === FrameType.Native) { + this.nativeFrameCount++; + } this.pushFrame(null, 0, 0, null, frameType); } @@ -394,6 +400,9 @@ module J2ME { } popMarkerFrame(frameType: FrameType): MethodInfo { + if (frameType === FrameType.Native) { + this.nativeFrameCount--; + } return this.popFrame(undefined, frameType); } @@ -525,26 +534,29 @@ module J2ME { // We should have a |PushPendingFrames| marker frame on the stack at this point. this.popMarkerFrame(FrameType.PushPendingFrames); - var pendingNativeFrame = null; + var pendingNativeFrameAddress = null; var frames = []; - while (pendingNativeFrame = this.pendingNativeFrames.pop()) { - frames.push(pendingNativeFrame); + while (pendingNativeFrameAddress = this.pendingNativeFrames.pop()) { + frames.push(pendingNativeFrameAddress); } - while (pendingNativeFrame = frames.pop()) { - traceWriter && traceWriter.writeLn("Pushing frame: " + pendingNativeFrame.methodInfo.implKey); - this.pushFrame(pendingNativeFrame.methodInfo, pendingNativeFrame.stack.length, pendingNativeFrame.pc, pendingNativeFrame.lockObject); - // TODO: Figure out how to copy floats and doubles. + while (pendingNativeFrameAddress = frames.pop()) { + var methodInfo = methodIdToMethodInfoMap[i32[pendingNativeFrameAddress + BailoutFrameLayout.MethodInfoIdOffset >> 2]]; + var stackCount = i32[pendingNativeFrameAddress + BailoutFrameLayout.StackCountOffset >> 2]; + var localCount = i32[pendingNativeFrameAddress + BailoutFrameLayout.LocalCountOffset >> 2]; + var pc = i32[pendingNativeFrameAddress + BailoutFrameLayout.PCOffset >> 2]; + var lockObjectAddress = i32[pendingNativeFrameAddress + BailoutFrameLayout.LockOffset >> 2]; + traceWriter && traceWriter.writeLn("Pushing frame: " + methodInfo.implKey); + this.pushFrame(methodInfo, stackCount, pc, lockObjectAddress); var frame = this.frame; - for (var j = 0; j < pendingNativeFrame.local.length; j++) { - var value = pendingNativeFrame.local[j]; - var kind = typeof value === "object" ? Kind.Reference : Kind.Int; - frame.setParameter(kind, j, value); + for (var j = 0; j < localCount; j++) { + var value = i32[(pendingNativeFrameAddress + BailoutFrameLayout.HeaderSize >> 2) + j]; + frame.setParameter(Kind.Int, j, value); } - for (var j = 0; j < pendingNativeFrame.stack.length; j++) { - var value = pendingNativeFrame.stack[j]; - var kind = typeof value === "object" ? Kind.Reference : Kind.Int; - frame.setStackSlot(kind, j, value); + for (var j = 0; j < stackCount; j++) { + var value = i32[(pendingNativeFrameAddress + BailoutFrameLayout.HeaderSize >> 2) + j + localCount]; + frame.setStackSlot(Kind.Int, j, value); } + ASM._gcFree(pendingNativeFrameAddress); } var frameType = i32[this.fp + FrameLayout.FrameTypeOffset] & FrameLayout.FrameTypeMask; @@ -1575,6 +1587,7 @@ module J2ME { if (U) { traceWriter && traceWriter.writeLn("<< I Unwind: " + VMState[U]); release || assert(thread.unwoundNativeFrames.length, "Must have unwound frames."); + thread.nativeFrameCount--; i32[frameTypeOffset] = FrameType.PushPendingFrames; thread.unwoundNativeFrames.push(null); return; @@ -2081,7 +2094,7 @@ module J2ME { maxLocals = mi.codeAttribute.max_locals; lp = fp - maxLocals; release || traceWriter && traceWriter.outdent(); - release || traceWriter && traceWriter.writeLn(">> I " + lastMI.implKey); + release || traceWriter && traceWriter.writeLn("<< I " + lastMI.implKey); ci = mi.classInfo; cp = ci.constantPool; code = mi.codeAttribute.code; @@ -2232,6 +2245,7 @@ module J2ME { if (U) { traceWriter && traceWriter.writeLn("<< I Unwind: " + VMState[U]); release || assert(thread.unwoundNativeFrames.length, "Must have unwound frames."); + thread.nativeFrameCount--; i32[frameTypeOffset] = FrameType.PushPendingFrames; thread.unwoundNativeFrames.push(null); return; @@ -2317,6 +2331,13 @@ module J2ME { release || traceWriter && traceWriter.writeLn(e.stack); // release || traceWriter && traceWriter.writeLn(jsGlobal.getBacktrace()); + // If an exception is thrown from a native there will be a native marker frame at the top of the stack + // which will be cut off when the the fp is set on the thread below. To keep the nativeFrameCount in + // sync the native marker must be popped. + if (thread.fp > fp) { + release || assert(i32[thread.fp + FrameLayout.CallerFPOffset] === fp, "Only one extra frame is on the stack. " + (thread.fp - fp)); + thread.popMarkerFrame(FrameType.Native); + } thread.set(fp, sp, opPC); e = translateException(e); if (!e.classInfo) { diff --git a/jit/analyze.ts b/jit/analyze.ts index cf688ba9..a77254ce 100644 --- a/jit/analyze.ts +++ b/jit/analyze.ts @@ -67,6 +67,7 @@ module J2ME { "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, + "java/lang/Runtime.gc.()V": YieldReason.Root, // Test Files: "gnu/testlet/vm/NativeTest.throwExceptionAfterPause.()V": YieldReason.Root, "gnu/testlet/vm/NativeTest.returnAfterPause.()I": YieldReason.Root, diff --git a/jit/baseline.ts b/jit/baseline.ts index 40efbdc4..09109fc2 100644 --- a/jit/baseline.ts +++ b/jit/baseline.ts @@ -390,9 +390,7 @@ module J2ME { if (needsTry) { this.bodyEmitter.leaveAndEnter("}catch(ex){"); if (this.hasUnwindThrow) { - var local = this.local.join(","); - var stack = this.stack.join(","); - this.bodyEmitter.writeLn("if(U){$.T(" + this.methodInfo.id + ",ex,[" + local + "],[" + stack + "]," + this.lockObject + ");return;}"); + this.emitBailout(this.bodyEmitter, "ex.getPC()", "ex.getSP()", this.stack); } this.bodyEmitter.writeLn(this.getStackName(0) + "=TE(ex)._address;"); this.sp = 1; @@ -1139,13 +1137,25 @@ module J2ME { emitter.writeLn("U&&B" + this.sp + "(" + pc + ");"); this.hasUnwindThrow = true; } else { - var local = this.local.join(","); - var stack = BaselineCompiler.stackNames.slice(0, this.sp).join(","); - emitter.writeLn("if(U){$.B(" + this.methodInfo.id + "," + pc + ",[" + local + "],[" + stack + "]," + this.lockObject + ");return;}"); + this.emitBailout(emitter, pc, String(this.sp), BaselineCompiler.stackNames.slice(0, this.sp)); } baselineCounter && baselineCounter.count("emitUnwind"); } + private emitBailout(emitter: Emitter, pc: string, sp: string, stack: string[]) { + var localCount = this.local.length; + var args = [this.methodInfo.id, pc, localCount, sp, this.lockObject]; + for (var i = 0; i < localCount; i++) { + args.push(this.local[i]); + } + for (var i = 0; i < stack.length; i++) { + args.push(stack[i]); + } + emitter.writeLn("if(U){" + + "$.B(" + args.join(",") + ");" + + "return;}"); + } + emitNoUnwindAssertion() { this.blockEmitter.writeLn("if(U){J2ME.Debug.assert(false,'Unexpected unwind.');}"); } diff --git a/native.js.in b/native.js.in index 2d0f9b98..09489a79 100644 --- a/native.js.in +++ b/native.js.in @@ -505,7 +505,14 @@ Native["java/lang/Runtime.totalMemory.()J"] = function(addr) { }; Native["java/lang/Runtime.gc.()V"] = function(addr) { - ASM._forceCollection(); + // Force a bailout so that there are no native frames on the stack + // so GC can be safely run. + asyncImpl("V", new Promise(function(resolve, reject) { + setTimeout(function() { + ASM._forceCollection(); + resolve(); + }); + })); }; Native["java/lang/Math.floor.(D)D"] = function(addr, valLow, valHigh) { diff --git a/vm/context.ts b/vm/context.ts index 7159338d..a398b38c 100644 --- a/vm/context.ts +++ b/vm/context.ts @@ -317,6 +317,7 @@ module J2ME { // Rethrow so the exception is not silent. throw "classInfo" in e ? e.classInfo : e; } + release || assert(this.nativeThread.nativeFrameCount === 0, "All native frames should be gone.") if (U) { this.nativeThread.endUnwind(); switch (U) { @@ -453,9 +454,9 @@ module J2ME { this.unblock(monitor, "waiting", notifyAll); } - bailout(methodInfo: MethodInfo, pc: number, local: any [], stack: any [], lockObject: java.lang.Object) { - traceWriter && traceWriter.writeLn("Bailout: " + methodInfo.implKey); - this.nativeThread.unwoundNativeFrames.push({frameType: FrameType.Interpreter, methodInfo: methodInfo, pc: pc, local: local, stack: stack, lockObject: lockObject}); + bailout(bailoutFrameAddress: number) { + traceWriter && traceWriter.writeLn("Bailout: " + methodIdToMethodInfoMap[i32[bailoutFrameAddress + BailoutFrameLayout.MethodInfoIdOffset >> 2]].implKey); + this.nativeThread.unwoundNativeFrames.push(bailoutFrameAddress); } pauseMethodTimeline() { diff --git a/vm/native/native.cpp b/vm/native/native.cpp index 8b794120..e280a6bf 100644 --- a/vm/native/native.cpp +++ b/vm/native/native.cpp @@ -56,6 +56,15 @@ extern "C" { }, (int)obj); } + int stop() { + return EM_ASM_INT_V({ + if (!$) { + return 0; + } + return $.ctx.nativeThread.nativeFrameCount; + }); + } + uintptr_t gcMallocUncollectable(int32_t size) { return (uintptr_t)GC_MALLOC_UNCOLLECTABLE(size); } @@ -65,11 +74,11 @@ extern "C" { } uintptr_t gcMalloc(int32_t size) { - return (uintptr_t)GC_MALLOC_UNCOLLECTABLE(size); + return (uintptr_t)GC_MALLOC(size); } uintptr_t gcMallocAtomic(int32_t size) { - return (uintptr_t)GC_MALLOC_UNCOLLECTABLE(size); + return (uintptr_t)GC_MALLOC_ATOMIC(size); } void gcRegisterDisappearingLink(uintptr_t p, uintptr_t objAddr) { @@ -97,5 +106,6 @@ extern "C" { } int main() { + GC_set_stop_func(stop); GC_INIT(); } diff --git a/vm/runtime.ts b/vm/runtime.ts index 4e14d52a..3ccb2741 100644 --- a/vm/runtime.ts +++ b/vm/runtime.ts @@ -633,20 +633,17 @@ module J2ME { /** * Bailout callback whenever a JIT frame is unwound. */ - B(methodInfoId: number, pc: number, local: any [], stack: any [], lockObject: java.lang.Object) { - var methodInfo = methodIdToMethodInfoMap[methodInfoId]; - release || assert(methodInfo !== undefined); - this.ctx.bailout(methodInfo, pc, local, stack, lockObject); - } - - /** - * Bailout callback whenever a JIT frame is unwound that uses a slightly different calling - * convetion that makes it more convenient to emit in some cases. - */ - T(methodInfoId: number, location: UnwindThrowLocation, local: any [], stack: any [], lockObject: java.lang.Object) { - var methodInfo = methodIdToMethodInfoMap[methodInfoId]; - release || assert(methodInfo !== undefined); - this.ctx.bailout(methodInfo, location.getPC(), local, stack.slice(0, location.getSP()), lockObject); + B(methodInfoId: number, pc: number, localCount: number, stackCount: number, lockObjectAddress: number) { + // TODO: use specialized $.B functions based on the local and stack size so we don't have to use the arguments variable. + var bailoutFrameAddress = createBailoutFrame(methodInfoId, pc, localCount, stackCount, lockObjectAddress); + var argumentOffset = 5; + for (var j = 0; j < localCount; j++) { + i32[(bailoutFrameAddress + BailoutFrameLayout.HeaderSize >> 2) + j] = arguments[argumentOffset + j]; + } + for (var j = 0; j < stackCount; j++) { + i32[(bailoutFrameAddress + BailoutFrameLayout.HeaderSize >> 2) + j + localCount] = arguments[argumentOffset + localCount + j]; + } + this.ctx.bailout(bailoutFrameAddress); } SA(classId: number) { @@ -669,7 +666,8 @@ module J2ME { nativeBailout(returnKind: Kind, opCode?: Bytecode.Bytecodes) { var pc = returnKind === Kind.Void ? 0 : 1; var methodInfo = CLASSES.getUnwindMethodInfo(returnKind, opCode); - $.ctx.bailout(methodInfo, pc, [], [], null); + var bailoutFrameAddress = createBailoutFrame(methodInfo.id, pc, 0, 0, Constants.NULL); + this.ctx.bailout(bailoutFrameAddress); } pause(reason: string) { @@ -1196,6 +1194,26 @@ module J2ME { NativeMap.delete(addr); } + export enum BailoutFrameLayout { + MethodInfoIdOffset = 0, + PCOffset = 4, + LocalCountOffset = 8, + StackCountOffset = 12, + LockOffset = 16, + HeaderSize = 20 + } + + export function createBailoutFrame(methodInfoId: number, pc: number, localCount: number, stackCount: number, lockObjectAddress: number): number { + var address = ASM._gcMallocUncollectable(BailoutFrameLayout.HeaderSize + ((localCount + stackCount) << 2)); + release || assert(typeof methodInfoId === "number" && methodInfoId in methodIdToMethodInfoMap, "Must be valid method info."); + i32[address + BailoutFrameLayout.MethodInfoIdOffset >> 2] = methodInfoId; + i32[address + BailoutFrameLayout.PCOffset >> 2] = pc; + i32[address + BailoutFrameLayout.LocalCountOffset >> 2] = localCount; + i32[address + BailoutFrameLayout.StackCountOffset >> 2] = stackCount; + i32[address + BailoutFrameLayout.LockOffset >> 2] = lockObjectAddress; + return address; + } + /** * A map from Java object addresses to native objects. * @@ -1556,6 +1574,7 @@ module J2ME { getLinkedMethod(CLASSES.java_lang_Class.getMethodByNameString("initialize", "()V"))($.getClassObjectAddress(classInfo)); if (U) { i32[frameTypeOffset] = FrameType.PushPendingFrames; + thread.nativeFrameCount--; thread.unwoundNativeFrames.push(null); return; } From 6136507afe027ff92d2d4a8eb85be85635bc891c Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 11 Aug 2015 18:04:26 -0700 Subject: [PATCH 09/11] Fix tracing for unwinds. --- int.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/int.ts b/int.ts index 80d03992..205f04e6 100644 --- a/int.ts +++ b/int.ts @@ -513,7 +513,7 @@ module J2ME { tracePendingFrames(writer: IndentingWriter) { for (var i = 0; i < this.pendingNativeFrames.length; i++) { var pendingFrame = this.pendingNativeFrames[i]; - writer.writeLn(pendingFrame ? pendingFrame.methodInfo.implKey : "-marker-"); + writer.writeLn(pendingFrame ? methodIdToMethodInfoMap[i32[pendingFrame + BailoutFrameLayout.MethodInfoIdOffset >> 2]].implKey : "-marker-"); } } From c60ed0549b0e7d83c49b1892c1710c8f6e6237fd Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 11 Aug 2015 18:04:48 -0700 Subject: [PATCH 10/11] Disable OSR from interpreter frame adapters for now. --- int.ts | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/int.ts b/int.ts index 205f04e6..dd2f430b 100644 --- a/int.ts +++ b/int.ts @@ -1560,7 +1560,7 @@ module J2ME { // on stack replacement. var previousFrameType = i32[i32[fp + FrameLayout.CallerFPOffset] + FrameLayout.FrameTypeOffset] & FrameLayout.FrameTypeMask; - if ((previousFrameType === FrameType.Interpreter || previousFrameType === FrameType.ExitInterpreter) && mi.onStackReplacementEntryPoints.indexOf(opPC + jumpOffset) > -1) { + if ((previousFrameType === FrameType.Interpreter) && mi.onStackReplacementEntryPoints.indexOf(opPC + jumpOffset) > -1) { traceWriter && traceWriter.writeLn("OSR: " + mi.implKey); onStackReplacementCount++; @@ -1599,27 +1599,6 @@ module J2ME { kind = signatureKinds[0]; - if (previousFrameType === FrameType.ExitInterpreter) { - thread.set(fp, sp, opPC); - switch (kind) { - case Kind.Long: - case Kind.Double: - return returnLong(returnValue, tempReturn0); - case Kind.Int: - case Kind.Byte: - case Kind.Char: - case Kind.Float: - case Kind.Short: - case Kind.Boolean: - case Kind.Reference: - return returnValue; - case Kind.Void: - return; - default: - release || assert(false, "Invalid Kind: " + Kind[kind]); - } - } - mi = methodIdToMethodInfoMap[i32[fp + FrameLayout.CalleeMethodInfoOffset] & FrameLayout.CalleeMethodInfoMask]; type = i32[fp + FrameLayout.FrameTypeOffset] & FrameLayout.FrameTypeMask; From 08ff5275d0a9fc575193d927c1a98ed51abd32db Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 11 Aug 2015 18:05:07 -0700 Subject: [PATCH 11/11] Allow for case when exceptions are created. --- int.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/int.ts b/int.ts index dd2f430b..462c43e0 100644 --- a/int.ts +++ b/int.ts @@ -2313,7 +2313,7 @@ module J2ME { // If an exception is thrown from a native there will be a native marker frame at the top of the stack // which will be cut off when the the fp is set on the thread below. To keep the nativeFrameCount in // sync the native marker must be popped. - if (thread.fp > fp) { + if (thread.fp > fp && thread.frame.type === FrameType.Native) { release || assert(i32[thread.fp + FrameLayout.CallerFPOffset] === fp, "Only one extra frame is on the stack. " + (thread.fp - fp)); thread.popMarkerFrame(FrameType.Native); }