package cn.liutils.ripple.impl.compiler; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Stack; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import cn.liutils.ripple.Calculation; import cn.liutils.ripple.IFunction; import cn.liutils.ripple.Path; import cn.liutils.ripple.ScriptNamespace; import cn.liutils.ripple.RippleException.RippleCompilerException; import cn.liutils.ripple.ScriptProgram; import cn.liutils.ripple.ScriptStacktrace; /** * The code generator for a single function. Used by parser. * @author acaly, WeAthFold * */ public final class CodeGenerator { public IFunction testCompile() { this.addParameter("x"); this.functionBodyBegin(); this.pushIntegerConst(1); this.pushParameter("x"); this.calcBinary(BinaryOperator.ADD); return this.functionBodyEnd(); } private static class StackInfo { } private static class FunctionCallInfo { String path; } private static class SwitchBlockInfo { int localId; Label labelEnd; Label labelNext; boolean hasDefault; } private static class CachedFunctionInfo { int index; int nargs; String path; } private final ScriptProgram program; private final Parser parser; private final Path path; private final HashMap<String, Integer> paramMap = new HashMap(); private Type classType; private MethodVisitor methodVisitor; private ClassWriter classWriter; private ArrayList<CachedFunctionInfo> cachedFunctions = new ArrayList(); private Stack<StackInfo> tempVars = new Stack(); private Stack<FunctionCallInfo> suspendedFunctionCall = new Stack(); private Stack<SwitchBlockInfo> suspendedSwitchBlock = new Stack(); private static Integer classNextId = 0; private static final Type objectType = Type.getType(Object.class); private static final String funcCacheFieldPrefix = "funcCache_"; CodeGenerator(Parser parser, Path functionPath) { this.parser = parser; this.program = parser.program; this.path = functionPath; } void addParameter(String name) { if (methodVisitor != null) { throw new RippleCompilerException("Try to add parameter in function body", parser); } if (paramMap.size() >= 120) { throw new RippleCompilerException("Too many parameters", parser); } paramMap.put(name, paramMap.size()); } void functionBodyBegin() { int classId; synchronized (classNextId) { classId = 0;// classNextId++; } visitCodeBegin(Integer.toString(classId)); } IFunction functionBodyEnd() { this.popTemp(); methodVisitor.visitInsn(Opcodes.ARETURN); this.checkEmpty(); this.visitCodeEnd(); try { Class<?> clazz = new FunctionClassLoader().defineClass(classType.getClassName(), classWriter.toByteArray()); return (IFunction) clazz.getConstructor(ScriptProgram.class).newInstance(program); } catch (Throwable t) { throw new RippleCompilerException("Cannot generate function", this.parser, t); } } //STACK +1 void pushIntegerConst(int value) { this.newTemp(); methodVisitor.visitLdcInsn(value); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), "castObject", Type.getMethodDescriptor(objectType, Type.INT_TYPE)); } //STACK +1 void pushDoubleConst(double value) { this.newTemp(); methodVisitor.visitLdcInsn(value); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), "castObject", Type.getMethodDescriptor(objectType, Type.DOUBLE_TYPE)); } //STACK +1 void pushBooleanConst(boolean value) { this.newTemp(); if (value) { methodVisitor.visitInsn(Opcodes.ICONST_1); } else { methodVisitor.visitInsn(Opcodes.ICONST_0); } methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), "castObject", Type.getMethodDescriptor(objectType, Type.BOOLEAN_TYPE)); } //STACK +1 void pushParameter(String name) { this.newTemp(); Integer id = paramMap.get(name); if (id == null) { throw new RippleCompilerException("Parameter not found: " + name, this.parser); } methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); methodVisitor.visitIntInsn(Opcodes.BIPUSH, id); methodVisitor.visitInsn(Opcodes.AALOAD); } //STACK -2+1 void calcBinary(BinaryOperator op) { this.mergeTemp(); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), op.methodName, Type.getMethodDescriptor(objectType, objectType, objectType)); } //STACK -1+1 void calcUnary(UnaryOperator op) { if (op.caseOp == null) { this.transformTemp(); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), op.methodName, Type.getMethodDescriptor(objectType, objectType)); } else { this.pushSwitchValue(); //swap methodVisitor.visitInsn(Opcodes.DUP_X1); methodVisitor.visitInsn(Opcodes.POP); this.calcBinary(op.caseOp); } } //STACK 0 void beforeCallFunction(Path path) { suspendedFunctionCall.push(new FunctionCallInfo()).path = path.path; } //STACK -nargs+1 void afterCallFunction(int nargs) { if (nargs > 250) { throw new RippleCompilerException("Too many arguments", this.parser); } String path = suspendedFunctionCall.pop().path; this.mergeTemp(nargs); methodVisitor.visitIntInsn(Opcodes.BIPUSH, nargs); methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, objectType.getInternalName()); //wrap arguments for (int i = nargs - 1; i >= 0; --i) { methodVisitor.visitInsn(Opcodes.DUP_X1); methodVisitor.visitInsn(Opcodes.DUP_X1); methodVisitor.visitInsn(Opcodes.POP); methodVisitor.visitIntInsn(Opcodes.BIPUSH, i); methodVisitor.visitInsn(Opcodes.DUP_X1); methodVisitor.visitInsn(Opcodes.POP); methodVisitor.visitInsn(Opcodes.AASTORE); } //get method id int id = cacheFunction(path, nargs); //push IFunction methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classType.getInternalName(), funcCacheFieldPrefix + id, Type.getDescriptor(IFunction.class)); //exchange IFunction and arguments methodVisitor.visitInsn(Opcodes.DUP_X1); methodVisitor.visitInsn(Opcodes.POP); //push frame methodVisitor.visitLdcInsn(path); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ScriptStacktrace.class), "pushFrame", Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(String.class))); methodVisitor.visitInsn(Opcodes.POP); //call methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(IFunction.class), "call", Type.getMethodDescriptor(objectType, Type.getType(Object[].class))); //pop frame methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ScriptStacktrace.class), "popFrame", Type.getMethodDescriptor(Type.VOID_TYPE)); } //STACK -1 void pushSwitchBlock() { this.popTemp(); SwitchBlockInfo s = new SwitchBlockInfo(); s.localId = suspendedSwitchBlock.size() + 2; //local starts from 2 (this, args) s.labelEnd = new Label(); suspendedSwitchBlock.push(s); methodVisitor.visitVarInsn(Opcodes.ASTORE, s.localId); } //STACK +1 void popSwitchBlock() { SwitchBlockInfo s = suspendedSwitchBlock.pop(); if (!s.hasDefault) { //throw exception methodVisitor.visitLdcInsn("Invalid switch input value"); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), "throwRuntimeException", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class))); //We have to push a value onto the stack, or jvm will throw an exception in control flow analysis methodVisitor.visitInsn(Opcodes.ACONST_NULL); } this.newTemp(); methodVisitor.visitLabel(s.labelEnd); } private void pushSwitchValue() { SwitchBlockInfo s = suspendedSwitchBlock.peek(); this.newTemp(); methodVisitor.visitVarInsn(Opcodes.ALOAD, s.localId); } //STACK -1 void switchCase(boolean hasWhen) { SwitchBlockInfo s = suspendedSwitchBlock.peek(); if (s.hasDefault) { throw new RippleCompilerException("Default must be the last case", this.parser); } this.popTemp(); if (!hasWhen) { //first compare this.pushSwitchValue(); this.calcBinary(BinaryOperator.EQUAL); } s.labelNext = new Label(); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), "castBoolean", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, objectType)); methodVisitor.visitJumpInsn(Opcodes.IFEQ, s.labelNext); } //STACK 0 void switchCaseDefault() { SwitchBlockInfo s = suspendedSwitchBlock.peek(); if (s.hasDefault) { throw new RippleCompilerException("Default must be the last case", this.parser); } s.hasDefault = true; } //STACK -1 void switchCaseEnd() { SwitchBlockInfo s = suspendedSwitchBlock.peek(); this.popTemp(); if (!s.hasDefault) { //not the default case methodVisitor.visitJumpInsn(Opcodes.GOTO, s.labelEnd); methodVisitor.visitLabel(s.labelNext); } } //Internal helpers private int cacheFunction(String path, int nargs) { for (CachedFunctionInfo f : cachedFunctions) { if (f.path.equals(path) && f.nargs == nargs) { return f.index; } } CachedFunctionInfo f = new CachedFunctionInfo(); f.index = cachedFunctions.size(); f.path = path; f.nargs = nargs; cachedFunctions.add(f); return f.index; } private void newTemp() { tempVars.push(new StackInfo()); } private void popTemp() { if (tempVars.empty()) { throw new RippleCompilerException("Stack error", this.parser); } tempVars.pop(); } private void mergeTemp() { if (tempVars.empty()) { throw new RippleCompilerException("Stack error", this.parser); } tempVars.pop(); } private void mergeTemp(int merged) { merged = merged - 1; for (int i = 0; i < merged; ++i) { if (tempVars.empty()) { throw new RippleCompilerException("Stack error", this.parser); } tempVars.pop(); } } private void transformTemp() { if (tempVars.empty()) { throw new RippleCompilerException("Stack error", this.parser); } } private void swapTopTemp() { if (tempVars.size() < 2) { throw new RippleCompilerException("Stack error", this.parser); } } private boolean checkEmpty() { return tempVars.empty() && suspendedSwitchBlock.empty() && suspendedFunctionCall.empty(); } private void visitCodeBegin(String id) { classWriter = new ClassWriter(0); classType = Type.getType("cn/liutils/ripple/Function_" + id); //class ? implements IFunction classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, classType.getInternalName(), null, objectType.getInternalName(), new String[]{ Type.getInternalName(IFunction.class) }); //public ?(ScriptProgram program) { { MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ScriptProgram.class)), null, null); //super(); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, objectType.getInternalName(), "<init>", "()V"); //this.program = program; mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitFieldInsn(Opcodes.PUTFIELD, classType.getInternalName(), "program", Type.getDescriptor(ScriptProgram.class)); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(100, 100); mv.visitEnd(); } //} //private ScriptProgram program; classWriter.visitField(Opcodes.ACC_PRIVATE, "program", Type.getDescriptor(ScriptProgram.class), null, null); //public Object call(Object[] params) { methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "call", Type.getMethodDescriptor(objectType, Type.getType(Object[].class)), null, null); methodVisitor.visitCode(); // this.setupCache(); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, classType.getInternalName(), "setupCache", Type.getMethodDescriptor(Type.VOID_TYPE)); } private void visitCodeEnd() { //} methodVisitor.visitMaxs(100, 100); methodVisitor.visitEnd(); //private IFunction funcCache_?; { int count = cachedFunctions.size(); for (int i = 0; i < count; ++i) { classWriter.visitField( Opcodes.ACC_PRIVATE, funcCacheFieldPrefix + Integer.toString(i), Type.getDescriptor(IFunction.class), null, null); } } //private void setupCache() { { MethodVisitor mv = classWriter.visitMethod( Opcodes.ACC_PRIVATE, "setupCache", Type.getMethodDescriptor(Type.VOID_TYPE), null, null); mv.visitCode(); for (CachedFunctionInfo func : cachedFunctions) { //funcCache_? = Calculation.getFunctionOverload(this.program, "?", ?); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitInsn(Opcodes.DUP); mv.visitFieldInsn(Opcodes.GETFIELD, classType.getInternalName(), "program", Type.getDescriptor(ScriptProgram.class)); //arg1 mv.visitLdcInsn(func.path);//arg2 mv.visitLdcInsn(path.path);//arg3 mv.visitLdcInsn(func.nargs);//arg4 mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Calculation.class), "getFunctionOverload", Type.getMethodDescriptor(Type.getType(IFunction.class), Type.getType(ScriptProgram.class), Type.getType(String.class), Type.getType(String.class), Type.INT_TYPE)); mv.visitFieldInsn(Opcodes.PUTFIELD, classType.getInternalName(), funcCacheFieldPrefix + Integer.toString(func.index), Type.getDescriptor(IFunction.class)); } mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(100, 100); mv.visitEnd(); } //} classWriter.visitEnd(); } }