diff --git a/actors.ts b/actors.ts index 32e2ecca..b5d19659 100644 --- a/actors.ts +++ b/actors.ts @@ -287,6 +287,7 @@ module J2ME { static createFromObject(object) { var classInfo = Object.create(ClassInfo.prototype, object); classInfo.resolved_constant_pool = new Array(classInfo.constant_pool.length); + classInfo.mangledName = mangleClass(classInfo); return classInfo; } @@ -389,16 +390,6 @@ module J2ME { } private _mangleFields() { - if (false) { - // Safe mangling that includes className, fieldName and signature. - var fields = this.fields; - for (var j = 0; j < fields.length; j++) { - var fieldInfo = fields[j]; - fieldInfo.mangledName = "$" + escapeString(fieldInfo.classInfo.className + "_" + fieldInfo.name + "_" + fieldInfo.signature); - } - return; - } - // Keep track of how many times a field name was used and resolve conflicts by // prefixing filed names with numbers. var classInfo: ClassInfo; @@ -546,11 +537,11 @@ module J2ME { } export class ArrayClassInfo extends ClassInfo { - constructor(className: string, elementClass?) { + constructor(className: string, elementClass: ClassInfo, mangledName?: string) { false && super(null); this.className = className; // TODO this may need to change for compiled code. - this.mangledName = className; + this.mangledName = mangledName; this.superClass = CLASSES.java_lang_Object; this.superClassName = "java/lang/Object"; this.access_flags = 0; @@ -589,22 +580,22 @@ module J2ME { PrimitiveClassInfo.prototype.interfaces = []; export class PrimitiveArrayClassInfo extends ArrayClassInfo { - constructor(className: string, elementClass?) { - super(className, elementClass); + constructor(className: string, elementClass: ClassInfo, mangledName: string) { + super(className, elementClass, mangledName); } get superClass() { return CLASSES.java_lang_Object; } - static Z = new PrimitiveArrayClassInfo("[Z", PrimitiveClassInfo.Z); - static C = new PrimitiveArrayClassInfo("[C", PrimitiveClassInfo.C); - static F = new PrimitiveArrayClassInfo("[F", PrimitiveClassInfo.F); - static D = new PrimitiveArrayClassInfo("[D", PrimitiveClassInfo.D); - static B = new PrimitiveArrayClassInfo("[B", PrimitiveClassInfo.B); - static S = new PrimitiveArrayClassInfo("[S", PrimitiveClassInfo.S); - static I = new PrimitiveArrayClassInfo("[I", PrimitiveClassInfo.I); - static J = new PrimitiveArrayClassInfo("[J", PrimitiveClassInfo.J); + static Z = new PrimitiveArrayClassInfo("[Z", PrimitiveClassInfo.Z, "Uint8Array"); + static C = new PrimitiveArrayClassInfo("[C", PrimitiveClassInfo.C, "Uint16Array"); + static F = new PrimitiveArrayClassInfo("[F", PrimitiveClassInfo.F, "Float32Array"); + static D = new PrimitiveArrayClassInfo("[D", PrimitiveClassInfo.D, "Float64Array"); + static B = new PrimitiveArrayClassInfo("[B", PrimitiveClassInfo.B, "Int8Array"); + static S = new PrimitiveArrayClassInfo("[S", PrimitiveClassInfo.S, "Int16Array"); + static I = new PrimitiveArrayClassInfo("[I", PrimitiveClassInfo.I, "Int32Array"); + static J = new PrimitiveArrayClassInfo("[J", PrimitiveClassInfo.J, "Int64Array"); } PrimitiveClassInfo.prototype.fields = []; diff --git a/jit/baseline.ts b/jit/baseline.ts index 7a7d187e..62fa0673 100644 --- a/jit/baseline.ts +++ b/jit/baseline.ts @@ -169,6 +169,17 @@ module J2ME { return "Long.fromInt(" + v + ")"; } + function classConstant(classInfo: ClassInfo): string { + if (classInfo.mangledName) { + return classInfo.mangledName; + } + if (classInfo.isArrayClass) { + return "$AK(" + classConstant(classInfo.elementClass) + ")"; + } + release || assert(classInfo.mangledName); + return classInfo.mangledName; + } + export class BaselineCompiler { sp: number; pc: number; @@ -308,7 +319,7 @@ module J2ME { if (classInfo.isInterface) { check = "$IOI"; } - check += "(" + this.peek(Kind.Reference) + ", " + mangleClass(classInfo) + ")"; + check += "(" + this.peek(Kind.Reference) + ", " + classConstant(classInfo) + ")"; check = " && " + check; } this.emitter.enter("if (pc >= " + handler.start_pc + " && pc < " + handler.end_pc + check + ") {"); @@ -591,11 +602,11 @@ module J2ME { } runtimeClass(classInfo: ClassInfo) { - return "$." + mangleClass(classInfo); + return "$." + classConstant(classInfo); } runtimeClassObject(classInfo: ClassInfo) { - return "$." + mangleClass(classInfo) + ".classObject"; + return "$." + classConstant(classInfo) + ".classObject"; } emitClassInitializationCheck(classInfo: ClassInfo) { @@ -650,12 +661,12 @@ module J2ME { object = this.pop(Kind.Reference); if (opcode === Bytecodes.INVOKESPECIAL) { args.unshift(object); - call = mangleClassAndMethod(methodInfo) + ".call(" + args.join(", ") + ")"; + call = methodInfo.mangledClassAndMethodName + ".call(" + args.join(", ") + ")"; } else { - call = object + "." + mangleMethod(methodInfo) + "(" + args.join(", ") + ")"; + call = object + "." + methodInfo.mangledName + "(" + args.join(", ") + ")"; } } else { - call = mangleClassAndMethod(methodInfo) + "(" + args.join(", ") + ")"; + call = methodInfo.mangledClassAndMethodName + "(" + args.join(", ") + ")"; } if (methodInfo.implKey in inlineMethods) { emitDebugInfoComments && this.emitter.writeLn("// Inlining: " + methodInfo.implKey); @@ -733,7 +744,7 @@ module J2ME { emitNewInstance(cpi: number) { var classInfo = this.lookupClass(cpi); this.emitClassInitializationCheck(classInfo); - this.emitPush(Kind.Reference, "new " + mangleClass(classInfo)+ "()"); + this.emitPush(Kind.Reference, "new " + classConstant(classInfo)+ "()"); } emitNewTypeArray(typeCode: number) { @@ -749,7 +760,7 @@ module J2ME { if (classInfo.isInterface) { call = "$CCI"; } - this.emitter.writeLn(call + "(" + object + ", " + mangleClass(classInfo) + ");"); + this.emitter.writeLn(call + "(" + object + ", " + classConstant(classInfo) + ");"); } emitInstanceOf(cpi: number) { @@ -759,7 +770,7 @@ module J2ME { if (classInfo.isInterface) { call = "$IOI"; } - this.emitPush(Kind.Int, call + "(" + object + ", " + mangleClass(classInfo) + ") | 0"); + this.emitPush(Kind.Int, call + "(" + object + ", " + classConstant(classInfo) + ") | 0"); } emitArrayLength() { @@ -770,7 +781,7 @@ module J2ME { var classInfo = this.lookupClass(cpi); this.emitClassInitializationCheck(classInfo); var length = this.pop(Kind.Int); - this.emitPush(Kind.Reference, "$NA(" + mangleClass(classInfo) + ", " + length + ")"); + this.emitPush(Kind.Reference, "$NA(" + classConstant(classInfo) + ", " + length + ")"); } emitUnwind(pc: number, nextPC: number) { diff --git a/jit/compiler.ts b/jit/compiler.ts index c800af0f..a15d9f63 100644 --- a/jit/compiler.ts +++ b/jit/compiler.ts @@ -142,7 +142,7 @@ module J2ME { export function emitKlass(emitter: Emitter, classInfo: ClassInfo) { var writer = emitter.writer; - var mangledClassName = mangleClass(classInfo); + var mangledClassName = classInfo.mangledName; if (emitter.closure) { writer.writeLn("/** @constructor */"); } @@ -227,7 +227,7 @@ module J2ME { }); } - var mangledClassName = mangleClass(classInfo); + var mangledClassName = classInfo.mangledName; emitter.writer.writeLn(mangledClassName + ".classSymbols = [" + referencedClasses.map(classInfo => { return quote(classInfo.className); @@ -240,7 +240,7 @@ module J2ME { methodFilter: (methodInfo: MethodInfo) => boolean, ctx: Context): CompiledMethodInfo [] { var writer = emitter.writer; - var mangledClassName = mangleClass(classInfo); + var mangledClassName = classInfo.mangledName; if (!isIdentifierName(mangledClassName)) { mangledClassName = quote(mangledClassName); } @@ -276,7 +276,7 @@ module J2ME { if (!methodFilter(method)) { continue; } - var mangledMethodName = mangleMethod(method); + var mangledMethodName = method.mangledName; if (!isIdentifierName(mangledMethodName)) { mangledMethodName = quote(mangledMethodName); } @@ -285,7 +285,7 @@ module J2ME { continue; } try { - var mangledClassAndMethodName = mangleClassAndMethod(method); + var mangledClassAndMethodName = method.mangledClassAndMethodName; if (emitter.debugInfo) { writer.writeLn("// " + method.implKey + " (" + mangledClassAndMethodName + ") " + method.getSourceLocationForPC(0)); } diff --git a/runtime.ts b/runtime.ts index 5c4e8cad..061ed44f 100644 --- a/runtime.ts +++ b/runtime.ts @@ -226,6 +226,8 @@ module J2ME { declare var util; import assert = J2ME.Debug.assert; + import concat3 = StringUtilities.concat3; + import concat5 = StringUtilities.concat5; export enum RuntimeStatus { New = 1, @@ -240,7 +242,10 @@ module J2ME { Compiled } + var hashMap = Object.create(null); + var hashArray = new Int32Array(1024); + function hashString(s: string) { if (hashArray.length < s.length) { hashArray = new Int32Array((hashArray.length * 2 / 3) | 0); @@ -249,10 +254,18 @@ module J2ME { for (var i = 0; i < s.length; i++) { data[i] = s.charCodeAt(i); } - return HashUtilities.hashBytesTo32BitsAdler(data, 0, s.length); - } + var hash = HashUtilities.hashBytesTo32BitsMurmur(data, 0, s.length); + + if (!release) { // Check to see that no collisions have ever happened. + if (hashMap[hash] && hashMap[hash] !== s) { + assert(false, "This is very bad.") + } + hashMap[hash] = s; + } var friendlyMangledNames = true; + return hash; + } function isIdentifierChar(c: number): boolean { @@ -318,58 +331,55 @@ module J2ME { var stringHashes = Object.create(null); var stringHashCount = 0; + function hashStringStrong(s): string { + // Hash with Murmur hash. + var result = StringUtilities.variableLengthEncodeInt32(hashString(s)); + // Also use the length for some more precision. + result += StringUtilities.toEncoding(s.length & 0x3f); + return result; + } + export function hashStringToString(s: string) { if (stringHashCount > 1024) { - return StringUtilities.variableLengthEncodeInt32(hashString(s)); + return hashStringStrong(s); } var c = stringHashes[s]; if (c) { return c; } - c = stringHashes[s] = StringUtilities.variableLengthEncodeInt32(hashString(s)); + c = stringHashes[s] = hashStringStrong(s); stringHashCount ++; return c; } export function mangleClassAndMethod(methodInfo: MethodInfo) { - var name = methodInfo.classInfo.className + "_" + methodInfo.name + "_" + hashStringToString(methodInfo.signature); + var name = concat5(methodInfo.classInfo.className, "_", methodInfo.name, "_", hashStringToString(methodInfo.signature)); if (friendlyMangledNames) { return escapeString(name); } - var hash = hashString(name); - return StringUtilities.variableLengthEncodeInt32(hash); + return hashStringToString(name); } export function mangleMethod(methodInfo: MethodInfo) { - var name = methodInfo.name + "_" + hashStringToString(methodInfo.signature); + var name = concat3(methodInfo.name, "_", hashStringToString(methodInfo.signature)); if (friendlyMangledNames) { return escapeString(name); } - var hash = hashString(name); - return StringUtilities.variableLengthEncodeInt32(hash); + return "$" + hashStringToString(name); + } + + export function mangleClassName(name: string): string { + if (friendlyMangledNames) { + return "$" + escapeString(name); + } + return "$" + hashStringToString(name); } export function mangleClass(classInfo: ClassInfo) { - if (classInfo instanceof PrimitiveArrayClassInfo) { - switch (classInfo) { - case PrimitiveArrayClassInfo.Z: return "Uint8Array"; - case PrimitiveArrayClassInfo.C: return "Uint16Array"; - case PrimitiveArrayClassInfo.F: return "Float32Array"; - case PrimitiveArrayClassInfo.D: return "Float64Array"; - case PrimitiveArrayClassInfo.B: return "Int8Array"; - case PrimitiveArrayClassInfo.S: return "Int16Array"; - case PrimitiveArrayClassInfo.I: return "Int32Array"; - case PrimitiveArrayClassInfo.J: return "Int64Array"; - } - } else if (classInfo.isArrayClass) { - return "$AK(" + mangleClass(classInfo.elementClass) + ")"; - } else { - if (friendlyMangledNames) { - return "$" + escapeString(classInfo.className); - } - var hash = hashString(classInfo.className); - return "$" + StringUtilities.variableLengthEncodeInt32(hash); + if (classInfo.mangledName) { + return classInfo.mangledName; } + return mangleClassName(classInfo.className); } /** @@ -892,7 +902,7 @@ module J2ME { export function registerKlassSymbol(className: string) { // TODO: This needs to be kept in sync to how mangleClass works. - var mangledName = "$" + escapeString(className); + var mangledName = mangleClassName(className); if (RuntimeTemplate.prototype.hasOwnProperty(mangledName)) { return; } @@ -1025,7 +1035,7 @@ module J2ME { function makeKlassConstructor(classInfo: ClassInfo): Klass { var klass: Klass; - var mangledName = mangleClass(classInfo); + var mangledName = classInfo.mangledName; if (classInfo.isInterface) { klass = function () { Debug.unexpected("Should never be instantiated.") @@ -1085,7 +1095,7 @@ module J2ME { return; } enterTimeline("linkKlass", {classInfo: classInfo}); - var mangledName = mangleClass(classInfo); + var mangledName = classInfo.mangledName; var klass; classInfo.klass = klass = getKlass(classInfo); classInfo.klass.classInfo = classInfo; @@ -1235,9 +1245,7 @@ module J2ME { } function findCompiledMethod(klass: Klass, methodInfo: MethodInfo): Function { - var name = methodInfo.mangledClassAndMethodName; - var method = jsGlobal[name]; - return method; + return jsGlobal[methodInfo.mangledClassAndMethodName]; } /** @@ -1446,7 +1454,7 @@ module J2ME { return; } - var mangledClassAndMethodName = mangleClassAndMethod(methodInfo); + var mangledClassAndMethodName = methodInfo.mangledClassAndMethodName; compiledCount ++; diff --git a/utilities.ts b/utilities.ts index c4c6a402..e2b47092 100644 --- a/utilities.ts +++ b/utilities.ts @@ -965,6 +965,34 @@ module J2ME { } export module HashUtilities { + // https://github.com/garycourt/murmurhash-js + export function hashBytesTo32BitsMurmur(data: Uint8Array, offset: number, length: number) { + var l = length, h = 0x12345678 ^ l, i = offset, k; + while (l >= 4) { + k = + (data[i]) | + (data[++i] << 8) | + (data[++i] << 16) | + (data[++i] << 24); + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + k ^= k >>> 24; + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k; + l -= 4; + ++i; + } + switch (l) { + case 3: h ^= data[i + 2] << 16; + case 2: h ^= data[i + 1] << 8; + case 1: h ^= data[i]; + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + } + h ^= h >>> 13; + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + h ^= h >>> 15; + return h >>> 0; + } + export function hashBytesTo32BitsAdler(data: Uint8Array, offset: number, length: number): number { var a = 1; var b = 0;