зеркало из https://github.com/mozilla/pluotsorbet.git
479 строки
15 KiB
TypeScript
479 строки
15 KiB
TypeScript
module J2ME {
|
|
|
|
export class Emitter {
|
|
constructor(
|
|
public writer: IndentingWriter,
|
|
public closure: boolean,
|
|
public debugInfo: boolean,
|
|
public klassHeaderOnly: boolean = false,
|
|
public definitions: boolean = false
|
|
) {
|
|
// ...
|
|
}
|
|
}
|
|
|
|
function getClassInheritanceChain(classInfo: ClassInfo): ClassInfo [] {
|
|
var list = [];
|
|
var klass = classInfo;
|
|
while (klass) {
|
|
list.unshift(klass);
|
|
klass = klass.superClass;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
function typeDescriptorToDefinition(value: string): string {
|
|
var typeDescriptor = TypeDescriptor.parseTypeDescriptor(value, 0);
|
|
var type = "";
|
|
if (typeDescriptor.kind === Kind.Reference) {
|
|
var dimensions = TypeDescriptor.getArrayDimensions(typeDescriptor);
|
|
if (dimensions) {
|
|
var elementType = typeDescriptor.value.substring(dimensions);
|
|
var elementTypeDescriptor = TypeDescriptor.parseTypeDescriptor(elementType, 0);
|
|
dimensions --;
|
|
switch (elementTypeDescriptor.kind) {
|
|
case Kind.Int:
|
|
type = "Int32Array";
|
|
break;
|
|
case Kind.Char:
|
|
type = "Uint16Array";
|
|
break;
|
|
case Kind.Short:
|
|
type = "Int16Array";
|
|
break;
|
|
case Kind.Byte:
|
|
case Kind.Boolean:
|
|
type = "Int8Array";
|
|
break;
|
|
case Kind.Float:
|
|
type = "Float32Array";
|
|
break;
|
|
case Kind.Long:
|
|
type = "Array";
|
|
break;
|
|
case Kind.Double:
|
|
type = "Float64Array";
|
|
break;
|
|
default:
|
|
type = typeDescriptorToDefinition(elementType);
|
|
dimensions ++;
|
|
break;
|
|
}
|
|
} else {
|
|
type = typeDescriptor.value.substring(dimensions + 1, typeDescriptor.value.length - 1);
|
|
type = type.replace(/\//g, '.');
|
|
}
|
|
for (var i = 0; i < dimensions; i++) {
|
|
type += "[]";
|
|
}
|
|
} else {
|
|
switch (typeDescriptor.kind) {
|
|
case Kind.Boolean: return "boolean";
|
|
case Kind.Byte:
|
|
case Kind.Short:
|
|
case Kind.Char:
|
|
case Kind.Int:
|
|
case Kind.Float:
|
|
case Kind.Double:
|
|
return "number";
|
|
case Kind.Long:
|
|
return "number"; // Should be Long.
|
|
case Kind.Void:
|
|
return "void";
|
|
default: throw Debug.unexpected("Unknown kind: " + typeDescriptor.kind);
|
|
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
export function signatureToDefinition(signature: string, includeReturnType = true, excludeArgumentNames = false): string {
|
|
var types = SignatureDescriptor.makeSignatureDescriptor(signature).typeDescriptors;
|
|
var argumentNames = "abcdefghijklmnopqrstuvwxyz";
|
|
var i = 0;
|
|
var result;
|
|
if (excludeArgumentNames) {
|
|
result = "(" + types.slice(1).map(t => typeDescriptorToDefinition(t.value)).join(", ") + ")";
|
|
} else {
|
|
result = "(" + types.slice(1).map(t => argumentNames[i++] + ": " + typeDescriptorToDefinition(t.value)).join(", ") + ")";
|
|
}
|
|
J2ME.Debug.assert(i < argumentNames.length);
|
|
if (includeReturnType) {
|
|
result += " => " + typeDescriptorToDefinition(types[0].value);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
export function emitMethodDefinition(emitter: Emitter, methodInfo: MethodInfo) {
|
|
if (methodInfo.name === "<clinit>") {
|
|
return;
|
|
}
|
|
if (methodInfo.isStatic && methodInfo.classInfo.isInterface) {
|
|
return;
|
|
}
|
|
var isStaticString = methodInfo.isStatic ? "static " : "";
|
|
var isConstructor = methodInfo.name === "<init>";
|
|
if (isConstructor) {
|
|
// emitter.writer.writeLn("constructor" + signatureToDefinition(methodInfo.signature, false) + " {}");
|
|
} else {
|
|
var name = methodInfo.name + methodInfo.signature;
|
|
emitter.writer.writeLn(isStaticString + quote(name) + ": " + signatureToDefinition(methodInfo.signature) + ";");
|
|
}
|
|
}
|
|
|
|
export function emitFieldDefinition(emitter: Emitter, fieldInfo: FieldInfo) {
|
|
if (fieldInfo.isStatic && fieldInfo.classInfo.isInterface) {
|
|
return;
|
|
}
|
|
var isStaticString = fieldInfo.isStatic ? "static " : "";
|
|
emitter.writer.writeLn(isStaticString + fieldInfo.name + ": " + typeDescriptorToDefinition(fieldInfo.signature) + ";");
|
|
}
|
|
|
|
export function emitKlass(emitter: Emitter, classInfo: ClassInfo) {
|
|
var writer = emitter.writer;
|
|
var mangledClassName = mangleClass(classInfo);
|
|
if (emitter.closure) {
|
|
writer.writeLn("/** @constructor */");
|
|
}
|
|
|
|
function emitFields(fields: FieldInfo [], emitStatic: boolean) {
|
|
for (var i = 0; i < fields.length; i++) {
|
|
var fieldInfo = fields[i];
|
|
if (fieldInfo.isStatic !== emitStatic) {
|
|
continue;
|
|
}
|
|
var signature = TypeDescriptor.makeTypeDescriptor(fieldInfo.signature);
|
|
var kind = signature.kind;
|
|
var defaultValue;
|
|
switch (kind) {
|
|
case Kind.Reference:
|
|
defaultValue = "null";
|
|
break;
|
|
case Kind.Long:
|
|
defaultValue = "Long.ZERO";
|
|
break;
|
|
default:
|
|
defaultValue = "0";
|
|
break;
|
|
}
|
|
if (emitter.definitions) {
|
|
emitFieldDefinition(emitter, fieldInfo);
|
|
} else {
|
|
if (emitter.closure) {
|
|
writer.writeLn("this[" + quote(fieldInfo.mangledName) + "] = " + defaultValue + ";");
|
|
} else {
|
|
writer.writeLn("this." + fieldInfo.mangledName + " = " + defaultValue + ";");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (emitter.definitions) {
|
|
emitFields(classInfo.fields, false);
|
|
emitFields(classInfo.fields, true);
|
|
return;
|
|
}
|
|
|
|
// Emit class initializer.
|
|
writer.enter("function " + mangledClassName + "() {");
|
|
//
|
|
// Should we or should we not generate hash codes at this point? Eager or lazy, we should at least
|
|
// initialize it zero to keep object shapes fixed.
|
|
// writer.writeLn("this._hashCode = $.nextHashCode(this);");
|
|
writer.writeLn("this._hashCode = 0;");
|
|
getClassInheritanceChain(classInfo).forEach(function (ci) {
|
|
emitFields(ci.fields, false);
|
|
});
|
|
writer.leave("}");
|
|
|
|
// Emit class static initializer if it has any static fields. We don't emit this for now
|
|
// since it probably doesn't pay off to emit code that only gets executed once.
|
|
if (false && classInfo.fields.some(f => f.isStatic)) {
|
|
writer.enter(mangledClassName + ".staticInitializer = function() {");
|
|
emitFields(classInfo.fields, true);
|
|
writer.leave("}");
|
|
}
|
|
|
|
if (emitter.klassHeaderOnly) {
|
|
return;
|
|
}
|
|
|
|
if (emitter.closure) {
|
|
writer.writeLn("window[" + quote(mangledClassName) + "] = " + mangledClassName + ";");
|
|
}
|
|
}
|
|
|
|
function classNameWithDots(classInfo: ClassInfo) {
|
|
return classInfo.className.replace(/\//g, '.');
|
|
}
|
|
|
|
export function emitReferencedSymbols(emitter: Emitter, classInfo: ClassInfo, compiledMethods: CompiledMethodInfo []) {
|
|
var referencedClasses = [];
|
|
for (var i = 0; i < compiledMethods.length; i++) {
|
|
var compiledMethod = compiledMethods[i];
|
|
compiledMethod.referencedClasses.forEach(classInfo => {
|
|
ArrayUtilities.pushUnique(referencedClasses, classInfo);
|
|
});
|
|
}
|
|
|
|
var mangledClassName = mangleClass(classInfo);
|
|
|
|
emitter.writer.writeLn(mangledClassName + ".classSymbols = [" + referencedClasses.map(classInfo => {
|
|
return quote(classInfo.className);
|
|
}).join(", ") + "];");
|
|
}
|
|
|
|
function compileClassInfo(emitter: Emitter, classInfo: ClassInfo,
|
|
methodFilter: (methodInfo: MethodInfo) => boolean,
|
|
ctx: Context): CompiledMethodInfo [] {
|
|
var writer = emitter.writer;
|
|
var mangledClassName = mangleClass(classInfo);
|
|
if (!J2ME.C4.Backend.isIdentifierName(mangledClassName)) {
|
|
mangledClassName = quote(mangledClassName);
|
|
}
|
|
|
|
var classNameParts;
|
|
if (emitter.definitions) {
|
|
classNameParts = classInfo.className.split("/");
|
|
if (classNameParts.length > 1) {
|
|
writer.enter("module " + classNameParts.slice(0, classNameParts.length - 1).join(".") + " {");
|
|
}
|
|
var classOrInterfaceString = classInfo.isInterface ? "interface" : "class";
|
|
var extendsString = classInfo.superClass ? " extends " + classNameWithDots(classInfo.superClass) : "";
|
|
if (classInfo.isInterface) {
|
|
extendsString = "";
|
|
}
|
|
// var implementsString = classInfo.interfaces.length ? " implements " + classInfo.interfaces.map(i => classNameWithDots(i)).join(", ") : "";
|
|
var implementsString = "";
|
|
writer.enter("export " + classOrInterfaceString + " " + classNameParts[classNameParts.length - 1] + extendsString + implementsString + " {");
|
|
}
|
|
|
|
emitKlass(emitter, classInfo);
|
|
|
|
var methods = classInfo.methods;
|
|
var compiledMethods: CompiledMethodInfo [] = [];
|
|
for (var i = 0; i < methods.length; i++) {
|
|
var method = methods[i];
|
|
if (method.isNative) {
|
|
continue;
|
|
}
|
|
if (!method.code) {
|
|
continue;
|
|
}
|
|
if (!methodFilter(method)) {
|
|
continue;
|
|
}
|
|
var mangledMethodName = mangleMethod(method);
|
|
if (!J2ME.C4.Backend.isIdentifierName(mangledMethodName)) {
|
|
mangledMethodName = quote(mangledMethodName);
|
|
}
|
|
if (emitter.definitions) {
|
|
emitMethodDefinition(emitter, method);
|
|
continue;
|
|
}
|
|
try {
|
|
var mangledClassAndMethodName = mangleClassAndMethod(method);
|
|
if (emitter.debugInfo) {
|
|
writer.writeLn("// " + classInfo.className + "/" + method.name + " " + method.signature + " (" + mangledClassAndMethodName + ") " + method.getSourceLocationForPC(0));
|
|
}
|
|
var compiledMethod = undefined;
|
|
try {
|
|
compiledMethod = compileMethod(method, ctx, CompilationTarget.Static);
|
|
} catch (e) {
|
|
writer.writeLn("// Compiler Exception: " + e.toString());
|
|
}
|
|
if (compiledMethod && compiledMethod.body) {
|
|
var compiledMethodName = mangledClassAndMethodName;
|
|
writer.enter("function " + compiledMethodName + "(" + compiledMethod.args.join(",") + ") {");
|
|
writer.writeLns(compiledMethod.body);
|
|
writer.leave("}");
|
|
if (method.name === "<clinit>") {
|
|
writer.writeLn(mangledClassName + ".staticConstructor = " + mangledClassAndMethodName);
|
|
} else if (!method.isStatic) {
|
|
//if (emitter.closure) {
|
|
// writer.writeLn(mangledClassName + ".prototype[" + quote(mangledMethodName) + "] = " + mangledClassAndMethodName + ";");
|
|
//} else {
|
|
// writer.writeLn(mangledClassName + ".prototype." + mangledMethodName + " = " + mangledClassAndMethodName + ";");
|
|
//}
|
|
if (emitter.closure) {
|
|
writer.writeLn("window[" + quote(mangledClassAndMethodName) + "] = " + mangledClassAndMethodName + ";");
|
|
}
|
|
}
|
|
compiledMethods.push(compiledMethod);
|
|
}
|
|
} catch (x) {
|
|
stderrWriter.writeLn("XXXX: " + x);
|
|
stderrWriter.writeLn(x.stack);
|
|
}
|
|
}
|
|
|
|
emitReferencedSymbols(emitter, classInfo, compiledMethods);
|
|
|
|
if (emitter.definitions) {
|
|
if (classNameParts.length > 1) {
|
|
writer.leave("}");
|
|
}
|
|
writer.leave("}");
|
|
}
|
|
|
|
return compiledMethods;
|
|
}
|
|
|
|
export class CompiledMethodInfo {
|
|
constructor(public args: string [], public body: string, public referencedClasses: ClassInfo []) {
|
|
// ...
|
|
}
|
|
}
|
|
|
|
export function compileMethod(methodInfo: MethodInfo, ctx: Context, target: CompilationTarget): CompiledMethodInfo {
|
|
var method;
|
|
method = baselineCompileMethod(methodInfo, target);
|
|
return method;
|
|
try {
|
|
method = optimizerCompileMethod(methodInfo, target);
|
|
} catch (x) {
|
|
method = baselineCompileMethod(methodInfo, target);
|
|
}
|
|
return method;
|
|
}
|
|
|
|
export function compile(jvm: any,
|
|
jarFilter: (jarFile: string) => boolean,
|
|
classFilter: (classInfo: ClassInfo) => boolean,
|
|
methodFilter: (methodInfo: MethodInfo) => boolean,
|
|
fileFilter: string, debugInfo: boolean, tsDefinitions: boolean) {
|
|
var runtime = new Runtime(jvm);
|
|
var jarFiles = CLASSES.jarFiles;
|
|
var ctx = new Context(runtime);
|
|
var code = "";
|
|
var writer = new J2ME.IndentingWriter(false, function (s) {
|
|
code += s + "\n";
|
|
});
|
|
|
|
var emitter = new Emitter(writer, false, debugInfo, false, tsDefinitions);
|
|
|
|
var compiledMethods: CompiledMethodInfo [] = [];
|
|
var classInfoList: ClassInfo [] = [];
|
|
Object.keys(jarFiles).every(function (path) {
|
|
if (path.substr(-4) !== ".jar" || !jarFilter(path)) {
|
|
return true;
|
|
}
|
|
var zipFile = jarFiles[path];
|
|
Object.keys(zipFile.directory).every(function (fileName) {
|
|
if (fileName.substr(-6) !== '.class') {
|
|
return true;
|
|
}
|
|
try {
|
|
var className = fileName.substring(0, fileName.length - 6);
|
|
var classInfo = CLASSES.getClass(className);
|
|
if (classInfo.sourceFile && !classInfo.sourceFile.match(fileFilter)) {
|
|
return true;
|
|
}
|
|
if (!classFilter(classInfo)) {
|
|
return true;
|
|
}
|
|
classInfoList.push(classInfo);
|
|
} catch (e) {
|
|
stderrWriter.writeLn(e);
|
|
}
|
|
return true;
|
|
}.bind(this));
|
|
return true;
|
|
}.bind(this));
|
|
|
|
var orderedClassInfoList: ClassInfo [] = [];
|
|
|
|
function hasDependencies(list, classInfo) {
|
|
var superClass = classInfo.superClass;
|
|
if (!superClass && classInfo.interfaces.length === 0) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (list[i].className === superClass.className) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (var j = 0; j < classInfo.interfaces; j++) {
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (list[i].className === classInfo.interfaces[j].className) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
while (classInfoList.length) {
|
|
for (var i = 0; i < classInfoList.length; i++) {
|
|
var classInfo = classInfoList[i];
|
|
if (!hasDependencies(classInfoList, classInfo)) {
|
|
orderedClassInfoList.push(classInfo);
|
|
classInfoList.splice(i--, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < orderedClassInfoList.length; i++) {
|
|
var classInfo = orderedClassInfoList[i];
|
|
|
|
writer.writeLn("//// " + classInfo.className);
|
|
|
|
if (emitter.debugInfo) {
|
|
writer.writeLn("// " + classInfo.className + (classInfo.superClass ? " extends " + classInfo.superClass.className : ""));
|
|
}
|
|
// Don't compile interfaces.
|
|
if (classInfo.isInterface) {
|
|
continue;
|
|
}
|
|
ArrayUtilities.pushMany(compiledMethods, compileClassInfo(emitter, classInfo, methodFilter, ctx));
|
|
}
|
|
|
|
stdoutWriter.writeLn(code);
|
|
|
|
stdoutWriter.enter("/*");
|
|
baselineCounter && baselineCounter.traceSorted(stdoutWriter);
|
|
yieldCounter && yieldCounter.traceSorted(stdoutWriter);
|
|
stdoutWriter.enter("*/");
|
|
// yieldCounter.traceSorted(stdoutWriter);
|
|
|
|
// stdoutWriter.writeLn("Compiled " + baselineCompiled + " of " + baselineTotal);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|