diff -r d8807b6a636a -r b9318fe303cd vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Mon Sep 24 11:07:38 2012 +0200 @@ -0,0 +1,793 @@ +/* +Java 4 Browser Bytecode Translator +Copyright (C) 2012-2012 Jaroslav Tulach + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. Look for COPYING file in the top folder. +If not, see http://opensource.org/licenses/GPL-2.0. +*/ +package org.apidesign.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import static org.netbeans.modules.classfile.ByteCodes.*; +import org.netbeans.modules.classfile.CPClassInfo; +import org.netbeans.modules.classfile.CPEntry; +import org.netbeans.modules.classfile.CPFieldInfo; +import org.netbeans.modules.classfile.CPMethodInfo; +import org.netbeans.modules.classfile.CPStringInfo; +import org.netbeans.modules.classfile.ClassFile; +import org.netbeans.modules.classfile.ClassName; +import org.netbeans.modules.classfile.Code; +import org.netbeans.modules.classfile.Method; +import org.netbeans.modules.classfile.Parameter; +import org.netbeans.modules.classfile.Variable; + +/** Translator of the code inside class files to JavaScript. + * + * @author Jaroslav Tulach + */ +public final class ByteCodeToJavaScript { + private final ClassFile jc; + private final Appendable out; + private final Collection references; + + private ByteCodeToJavaScript( + ClassFile jc, Appendable out, Collection references + ) { + this.jc = jc; + this.out = out; + this.references = references; + } + + /** + * Converts a given class file to a JavaScript version. + * + * @param classFile input stream with code of the .class file + * @param out a {@link StringBuilder} or similar to generate the output to + * @param references a write only collection where the system adds list of + * other classes that were referenced and should be loaded in order the + * generated JavaScript code works properly. The names are in internal + * JVM form so String is java/lang/String. Can be null + * if one is not interested in knowing references + * @throws IOException if something goes wrong during read or write or translating + */ + + public static void compile( + InputStream classFile, Appendable out, + Collection references + ) throws IOException { + ClassFile jc = new ClassFile(classFile, true); + ByteCodeToJavaScript compiler = new ByteCodeToJavaScript( + jc, out, references + ); + List toInitilize = new ArrayList(); + for (Method m : jc.getMethods()) { + if (m.isStatic()) { + compiler.generateStaticMethod(m, toInitilize); + } else { + compiler.generateInstanceMethod(m); + } + } + for (Variable v : jc.getVariables()) { + if (v.isStatic()) { + compiler.generateStaticField(v); + } + } + + final String className = jc.getName().getInternalName().replace('/', '_'); + out.append("\nfunction ").append(className); + out.append("() {"); + for (Method m : jc.getMethods()) { + if (!m.isStatic()) { + compiler.generateMethodReference(m); + } + } + for (Variable v : jc.getVariables()) { + if (!v.isStatic()) { + out.append("\n this." + v.getName() + " = 0;"); + } + } + out.append("\n this.$instOf_").append(className).append(" = true;"); + out.append("\n}"); + ClassName sc = jc.getSuperClass(); + if (sc != null) { + out.append("\n").append(className) + .append(".prototype = new ").append(sc.getInternalName().replace('/', '_')); + } + for (String init : toInitilize) { + out.append("\n").append(init).append("();"); + } + } + private void generateStaticMethod(Method m, List toInitilize) throws IOException { + final String mn = findMethodName(m); + out.append("\nfunction ").append( + jc.getName().getInternalName().replace('/', '_') + ).append('_').append(mn); + if (mn.equals("classV")) { + toInitilize.add(jc.getName().getInternalName().replace('/', '_') + '_' + mn); + } + out.append('('); + String space = ""; + List args = m.getParameters(); + for (int index = 0, i = 0; i < args.size(); i++) { + out.append(space); + out.append("arg").append(String.valueOf(index)); + space = ","; + final String desc = args.get(i).getDescriptor(); + if ("D".equals(desc) || "J".equals(desc)) { + index += 2; + } else { + index++; + } + } + out.append(") {").append("\n"); + final Code code = m.getCode(); + if (code != null) { + int len = code.getMaxLocals(); + for (int index = args.size(), i = args.size(); i < len; i++) { + out.append(" var "); + out.append("arg").append(String.valueOf(i)).append(";\n"); + } + out.append(" var stack = new Array();\n"); + produceCode(code.getByteCodes()); + } else { + out.append(" /* no code found for ").append(m.getTypeSignature()).append(" */\n"); + } + out.append("}"); + } + + private void generateMethodReference(Method m) throws IOException { + final String name = findMethodName(m); + out.append("\n this.").append(name).append(" = ") + .append(jc.getName().getInternalName().replace('/', '_')) + .append('_').append(name).append(";"); + } + + private void generateInstanceMethod(Method m) throws IOException { + out.append("\nfunction ").append( + jc.getName().getInternalName().replace('/', '_') + ).append('_').append(findMethodName(m)); + out.append("(arg0"); + String space = ","; + List args = m.getParameters(); + for (int index = 1, i = 0; i < args.size(); i++) { + out.append(space); + out.append("arg").append(String.valueOf(index)); + final String desc = args.get(i).getDescriptor(); + if ("D".equals(desc) || "J".equals(desc)) { + index += 2; + } else { + index++; + } + } + out.append(") {").append("\n"); + final Code code = m.getCode(); + if (code != null) { + int len = code.getMaxLocals(); + for (int index = args.size(), i = args.size(); i < len; i++) { + out.append(" var "); + out.append("arg").append(String.valueOf(i + 1)).append(";\n"); + } + out.append(";\n var stack = new Array("); + out.append(Integer.toString(code.getMaxStack())); + out.append(");\n"); + produceCode(code.getByteCodes()); + } else { + out.append(" /* no code found for ").append(m.getTypeSignature()).append(" */\n"); + } + out.append("}"); + } + + private void produceCode(byte[] byteCodes) throws IOException { + out.append("\n var gt = 0;\n for(;;) switch(gt) {\n"); + for (int i = 0; i < byteCodes.length; i++) { + int prev = i; + out.append(" case " + i).append(": "); + final int c = (byteCodes[i] + 256) % 256; + switch (c) { + case bc_aload_0: + case bc_iload_0: + case bc_lload_0: + case bc_fload_0: + case bc_dload_0: + out.append("stack.push(arg0);"); + break; + case bc_aload_1: + case bc_iload_1: + case bc_lload_1: + case bc_fload_1: + case bc_dload_1: + out.append("stack.push(arg1);"); + break; + case bc_aload_2: + case bc_iload_2: + case bc_lload_2: + case bc_fload_2: + case bc_dload_2: + out.append("stack.push(arg2);"); + break; + case bc_aload_3: + case bc_iload_3: + case bc_lload_3: + case bc_fload_3: + case bc_dload_3: + out.append("stack.push(arg3);"); + break; + case bc_iload: + case bc_lload: + case bc_fload: + case bc_dload: + case bc_aload: { + final int indx = (byteCodes[++i] + 256) % 256; + out.append("stack.push(arg").append(indx + ");"); + break; + } + case bc_astore_0: + case bc_istore_0: + case bc_lstore_0: + case bc_fstore_0: + case bc_dstore_0: + out.append("arg0 = stack.pop();"); + break; + case bc_astore_1: + case bc_istore_1: + case bc_lstore_1: + case bc_fstore_1: + case bc_dstore_1: + out.append("arg1 = stack.pop();"); + break; + case bc_astore_2: + case bc_istore_2: + case bc_lstore_2: + case bc_fstore_2: + case bc_dstore_2: + out.append("arg2 = stack.pop();"); + break; + case bc_astore_3: + case bc_istore_3: + case bc_lstore_3: + case bc_fstore_3: + case bc_dstore_3: + out.append("arg3 = stack.pop();"); + break; + case bc_iadd: + case bc_ladd: + case bc_fadd: + case bc_dadd: + out.append("stack.push(stack.pop() + stack.pop());"); + break; + case bc_isub: + case bc_lsub: + case bc_fsub: + case bc_dsub: + out.append("{ var tmp = stack.pop(); stack.push(stack.pop() - tmp); }"); + break; + case bc_imul: + case bc_lmul: + case bc_fmul: + case bc_dmul: + out.append("stack.push(stack.pop() * stack.pop());"); + break; + case bc_idiv: + case bc_ldiv: + out.append("{ var tmp = stack.pop(); stack.push(Math.floor(stack.pop() / tmp)); }"); + break; + case bc_fdiv: + case bc_ddiv: + out.append("{ var tmp = stack.pop(); stack.push(stack.pop() / tmp); }"); + break; + case bc_iand: + case bc_land: + out.append("stack.push(stack.pop() & stack.pop());"); + break; + case bc_ior: + case bc_lor: + out.append("stack.push(stack.pop() | stack.pop());"); + break; + case bc_ixor: + case bc_lxor: + out.append("stack.push(stack.pop() ^ stack.pop());"); + break; + case bc_iinc: { + final int varIndx = (byteCodes[++i] + 256) % 256; + final int incrBy = (byteCodes[++i] + 256) % 256; + if (incrBy == 1) { + out.append("arg" + varIndx).append("++;"); + } else { + out.append("arg" + varIndx).append(" += " + incrBy).append(";"); + } + break; + } + case bc_return: + out.append("return;"); + break; + case bc_ireturn: + case bc_lreturn: + case bc_freturn: + case bc_dreturn: + case bc_areturn: + out.append("return stack.pop();"); + break; + case bc_i2l: + case bc_i2f: + case bc_i2d: + case bc_l2i: + // max int check? + case bc_l2f: + case bc_l2d: + case bc_f2d: + case bc_d2f: + out.append("/* number conversion */"); + break; + case bc_f2i: + case bc_f2l: + case bc_d2i: + case bc_d2l: + out.append("stack.push(Math.floor(stack.pop()));"); + break; + case bc_i2b: + case bc_i2c: + case bc_i2s: + out.append("/* number conversion */"); + break; + case bc_iconst_0: + case bc_dconst_0: + case bc_lconst_0: + case bc_fconst_0: + out.append("stack.push(0);"); + break; + case bc_iconst_1: + case bc_lconst_1: + case bc_fconst_1: + case bc_dconst_1: + out.append("stack.push(1);"); + break; + case bc_iconst_2: + case bc_fconst_2: + out.append("stack.push(2);"); + break; + case bc_iconst_3: + out.append("stack.push(3);"); + break; + case bc_iconst_4: + out.append("stack.push(4);"); + break; + case bc_iconst_5: + out.append("stack.push(5);"); + break; + case bc_ldc: { + int indx = byteCodes[++i]; + CPEntry entry = jc.getConstantPool().get(indx); + String v = encodeConstant(entry); + out.append("stack.push(").append(v).append(");"); + break; + } + case bc_ldc_w: + case bc_ldc2_w: { + int indx = readIntArg(byteCodes, i); + CPEntry entry = jc.getConstantPool().get(indx); + i += 2; + String v = encodeConstant(entry); + out.append("stack.push(").append(v).append(");"); + break; + } + case bc_lcmp: + case bc_fcmpl: + case bc_fcmpg: + case bc_dcmpl: + case bc_dcmpg: { + out.append("{ var delta = stack.pop() - stack.pop(); stack.push(delta < 0 ?-1 : (delta == 0 ? 0 : 1)); }"); + break; + } + case bc_if_icmpeq: { + i = generateIf(byteCodes, i, "=="); + break; + } + case bc_ifeq: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() == 0) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_ifne: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() != 0) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_iflt: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() < 0) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_ifle: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() <= 0) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_ifgt: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() > 0) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_ifge: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() >= 0) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_ifnonnull: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop()) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_ifnull: { + int indx = i + readIntArg(byteCodes, i); + out.append("if (!stack.pop()) { gt = " + indx); + out.append("; continue; }"); + i += 2; + break; + } + case bc_if_icmpne: + i = generateIf(byteCodes, i, "!="); + break; + case bc_if_icmplt: + i = generateIf(byteCodes, i, ">"); + break; + case bc_if_icmple: + i = generateIf(byteCodes, i, ">="); + break; + case bc_if_icmpgt: + i = generateIf(byteCodes, i, "<"); + break; + case bc_if_icmpge: + i = generateIf(byteCodes, i, "<="); + break; + case bc_goto: { + int indx = i + readIntArg(byteCodes, i); + out.append("gt = " + indx).append("; continue;"); + i += 2; + break; + } + case bc_invokeinterface: + case bc_invokevirtual: + i = invokeVirtualMethod(byteCodes, i); + break; + case bc_invokespecial: + i = invokeStaticMethod(byteCodes, i, false); + break; + case bc_invokestatic: + i = invokeStaticMethod(byteCodes, i, true); + break; + case bc_new: { + int indx = readIntArg(byteCodes, i); + CPClassInfo ci = jc.getConstantPool().getClass(indx); + out.append("stack.push("); + out.append("new ").append(ci.getClassName().getInternalName().replace('/','_')); + out.append(");"); + addReference(ci.getClassName().getInternalName()); + i += 2; + break; + } + case bc_newarray: { + int type = byteCodes[i++]; + out.append("stack.push(new Array(stack.pop()));"); + break; + } + case bc_anewarray: { + i += 2; // skip type of array + out.append("stack.push(new Array(stack.pop()));"); + break; + } + case bc_arraylength: + out.append("stack.push(stack.pop().length);"); + break; + case bc_iastore: + case bc_lastore: + case bc_fastore: + case bc_dastore: + case bc_aastore: + case bc_bastore: + case bc_castore: + case bc_sastore: { + out.append("{ var value = stack.pop(); var indx = stack.pop(); stack.pop()[indx] = value; }"); + break; + } + case bc_iaload: + case bc_laload: + case bc_faload: + case bc_daload: + case bc_aaload: + case bc_baload: + case bc_caload: + case bc_saload: { + out.append("{ var indx = stack.pop(); stack.push(stack.pop()[indx]); }"); + break; + } + case bc_dup: + out.append("stack.push(stack[stack.length - 1]);"); + break; + case bc_bipush: + out.append("stack.push(" + byteCodes[++i] + ");"); + break; + case bc_getfield: { + int indx = readIntArg(byteCodes, i); + CPFieldInfo fi = (CPFieldInfo) jc.getConstantPool().get(indx); + out.append("stack.push(stack.pop().").append(fi.getFieldName()).append(");"); + i += 2; + break; + } + case bc_getstatic: { + int indx = readIntArg(byteCodes, i); + CPFieldInfo fi = (CPFieldInfo) jc.getConstantPool().get(indx); + final String in = fi.getClassName().getInternalName(); + out.append("stack.push(").append(in.replace('/', '_')); + out.append('_').append(fi.getFieldName()).append(");"); + i += 2; + addReference(in); + break; + } + case bc_putstatic: { + int indx = readIntArg(byteCodes, i); + CPFieldInfo fi = (CPFieldInfo) jc.getConstantPool().get(indx); + final String in = fi.getClassName().getInternalName(); + out.append(in.replace('/', '_')); + out.append('_').append(fi.getFieldName()).append(" = stack.pop();"); + i += 2; + addReference(in); + break; + } + case bc_putfield: { + int indx = readIntArg(byteCodes, i); + CPFieldInfo fi = (CPFieldInfo) jc.getConstantPool().get(indx); + out.append("{ var v = stack.pop(); stack.pop().") + .append(fi.getFieldName()).append(" = v; }"); + i += 2; + break; + } + case bc_instanceof: { + int indx = readIntArg(byteCodes, i); + CPClassInfo ci = jc.getConstantPool().getClass(indx); + out.append("stack.push(stack.pop().$instOf_") + .append(ci.getClassName().getInternalName().replace('/', '_')) + .append(" ? 1 : 0);"); + i += 2; + } + + } + out.append(" /*"); + for (int j = prev; j <= i; j++) { + out.append(" "); + final int cc = (byteCodes[j] + 256) % 256; + out.append(Integer.toString(cc)); + } + out.append("*/\n"); + } + out.append(" }\n"); + } + + private int generateIf(byte[] byteCodes, int i, final String test) throws IOException { + int indx = i + readIntArg(byteCodes, i); + out.append("if (stack.pop() ").append(test).append(" stack.pop()) { gt = " + indx); + out.append("; continue; }"); + return i + 2; + } + + private int readIntArg(byte[] byteCodes, int offsetInstruction) { + final int indxHi = byteCodes[offsetInstruction + 1] << 8; + final int indxLo = byteCodes[offsetInstruction + 2]; + return (indxHi & 0xffffff00) | (indxLo & 0xff); + } + + private static int countArgs(String descriptor, boolean[] hasReturnType, StringBuilder sig) { + int cnt = 0; + int i = 0; + Boolean count = null; + int firstPos = sig.length(); + while (i < descriptor.length()) { + char ch = descriptor.charAt(i++); + switch (ch) { + case '(': + count = true; + continue; + case ')': + count = false; + continue; + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + case 'Z': + if (count) { + cnt++; + sig.append(ch); + } else { + hasReturnType[0] = true; + sig.insert(firstPos, ch); + } + continue; + case 'V': + assert !count; + hasReturnType[0] = false; + sig.insert(firstPos, 'V'); + continue; + case 'L': + int next = descriptor.indexOf(';', i); + if (count) { + cnt++; + sig.append(ch); + sig.append(descriptor.substring(i, next).replace('/', '_')); + } else { + sig.insert(firstPos, descriptor.substring(i, next).replace('/', '_')); + sig.insert(firstPos, ch); + hasReturnType[0] = true; + } + i = next + 1; + continue; + case '[': + //arrays++; + continue; + default: + break; // invalid character + } + } + return cnt; + } + + private void generateStaticField(Variable v) throws IOException { + out.append("\nvar ") + .append(jc.getName().getInternalName().replace('/', '_')) + .append('_').append(v.getName()).append(" = 0;"); + } + + private String findMethodName(Method m) { + StringBuilder tmp = new StringBuilder(); + if ("".equals(m.getName())) { // NOI18N + tmp.append("consV"); // NOI18N + } else if ("".equals(m.getName())) { // NOI18N + tmp.append("classV"); // NOI18N + } else { + tmp.append(m.getName()); + outType(m.getReturnType(), tmp); + } + List args = m.getParameters(); + for (Parameter t : args) { + outType(t.getDescriptor(), tmp); + } + return tmp.toString(); + } + + private String findMethodName(CPMethodInfo mi, int[] cnt, boolean[] hasReturn) { + StringBuilder name = new StringBuilder(); + if ("".equals(mi.getName())) { // NOI18N + name.append("cons"); // NOI18N + } else { + name.append(mi.getName()); + } + cnt[0] = countArgs(mi.getDescriptor(), hasReturn, name); + return name.toString(); + } + + private int invokeStaticMethod(byte[] byteCodes, int i, boolean isStatic) + throws IOException { + int methodIndex = readIntArg(byteCodes, i); + CPMethodInfo mi = (CPMethodInfo) jc.getConstantPool().get(methodIndex); + boolean[] hasReturn = { false }; + int[] cnt = { 0 }; + String mn = findMethodName(mi, cnt, hasReturn); + out.append("{ "); + for (int j = cnt[0] - 1; j >= 0; j--) { + out.append("var v" + j).append(" = stack.pop(); "); + } + + if (hasReturn[0]) { + out.append("stack.push("); + } + final String in = mi.getClassName().getInternalName(); + out.append(in.replace('/', '_')); + out.append('_'); + out.append(mn); + out.append('('); + String sep = ""; + if (!isStatic) { + out.append("stack.pop()"); + sep = ", "; + } + for (int j = 0; j < cnt[0]; j++) { + out.append(sep); + out.append("v" + j); + sep = ", "; + } + out.append(")"); + if (hasReturn[0]) { + out.append(")"); + } + out.append("; }"); + i += 2; + addReference(in); + return i; + } + private int invokeVirtualMethod(byte[] byteCodes, int i) + throws IOException { + int methodIndex = readIntArg(byteCodes, i); + CPMethodInfo mi = (CPMethodInfo) jc.getConstantPool().get(methodIndex); + boolean[] hasReturn = { false }; + int[] cnt = { 0 }; + String mn = findMethodName(mi, cnt, hasReturn); + out.append("{ "); + for (int j = cnt[0] - 1; j >= 0; j--) { + out.append("var v" + j).append(" = stack.pop(); "); + } + out.append("var self = stack.pop(); "); + if (hasReturn[0]) { + out.append("stack.push("); + } + out.append("self."); + out.append(mn); + out.append('('); + out.append("self"); + for (int j = 0; j < cnt[0]; j++) { + out.append(", "); + out.append("v" + j); + } + out.append(")"); + if (hasReturn[0]) { + out.append(")"); + } + out.append("; }"); + i += 2; + return i; + } + + private void addReference(String cn) { + if (references != null) { + references.add(cn); + } + } + + private void outType(final String d, StringBuilder out) { + if (d.charAt(0) == 'L') { + assert d.charAt(d.length() - 1) == ';'; + out.append(d.replace('/', '_').substring(0, d.length() - 1)); + } else { + out.append(d); + } + } + + private String encodeConstant(CPEntry entry) { + final String v; + if (entry instanceof CPStringInfo) { + v = "\"" + entry.getValue().toString().replace("\"", "\\\"") + "\""; + } else { + v = entry.getValue().toString(); + } + return v; + } +}