package org.dynjs.ir; import me.qmx.jitescript.CodeBlock; import me.qmx.jitescript.JiteClass; import me.qmx.jitescript.internal.org.objectweb.asm.ClassReader; import me.qmx.jitescript.internal.org.objectweb.asm.Opcodes; import me.qmx.jitescript.internal.org.objectweb.asm.tree.LabelNode; import me.qmx.jitescript.internal.org.objectweb.asm.util.CheckClassAdapter; import org.dynjs.exception.DynJSException; import org.dynjs.ir.instructions.Add; import org.dynjs.ir.instructions.BEQ; import org.dynjs.ir.instructions.Call; import org.dynjs.ir.instructions.Copy; import org.dynjs.ir.instructions.DefineFunction; import org.dynjs.ir.instructions.Jump; import org.dynjs.ir.instructions.LT; import org.dynjs.ir.instructions.ReceiveFunctionParameter; import org.dynjs.ir.instructions.ResultInstruction; import org.dynjs.ir.instructions.Return; import org.dynjs.ir.instructions.Sub; import org.dynjs.ir.operands.BooleanLiteral; import org.dynjs.ir.operands.DynamicVariable; import org.dynjs.ir.operands.IntegerNumber; import org.dynjs.ir.operands.Label; import org.dynjs.ir.operands.LocalVariable; import org.dynjs.ir.operands.TemporaryVariable; import org.dynjs.ir.operands.Undefined; import org.dynjs.ir.operands.Variable; import org.dynjs.ir.representations.BasicBlock; import org.dynjs.runtime.AbstractFunction; import org.dynjs.runtime.DynamicClassLoader; import org.dynjs.runtime.ExecutionContext; import org.dynjs.runtime.GlobalContext; import org.dynjs.runtime.JSFunction; import org.dynjs.runtime.LexicalEnvironment; import org.dynjs.runtime.Reference; import org.dynjs.runtime.Types; import org.dynjs.runtime.VariableValues; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import static me.qmx.jitescript.util.CodegenUtils.ci; import static me.qmx.jitescript.util.CodegenUtils.p; import static me.qmx.jitescript.util.CodegenUtils.params; import static me.qmx.jitescript.util.CodegenUtils.sig; import static org.dynjs.codegen.CodeGeneratingVisitor.Arities.*; public class IRByteCodeCompiler { private final FunctionScope scope; private final String fileName; private final boolean strict; private final List<BasicBlock> blockList; private final AtomicInteger methodCounter = new AtomicInteger(); private static final AtomicInteger compiledFunctionCounter = new AtomicInteger(); public IRByteCodeCompiler(FunctionScope scope, String fileName, boolean strict) { this.scope = scope; this.fileName = fileName; this.strict = strict; this.blockList = scope.prepareForCompilation(); } private void emitInstruction(JiteClass jiteClass, HashMap<Label, LabelNode> jumpMap, CodeBlock block, Instruction instruction) { switch (instruction.getOperation()) { case DEFINE_FUNCTION: emitFunction(jiteClass, jumpMap, block, (DefineFunction) instruction); break; case ADD: emitAdd(block, (Add) instruction); break; case SUB: emitSub(block, (Sub) instruction); break; case COPY: emitCopy(block, (Copy) instruction); break; case BEQ: emitBEQ(block, (BEQ) instruction, jumpMap); break; case LT: emitLT(block, (LT) instruction); break; case JUMP: emitJump(block, (Jump) instruction, jumpMap); break; case RETURN: emitReturn(block, (Return) instruction, jumpMap); break; case RECEIVE_FUNCTION_PARAM: emitReceiveFunctionParameter(block, (ReceiveFunctionParameter) instruction, jumpMap); break; case CALL: emitCall(jiteClass, block, (Call) instruction, jumpMap); break; default: throw new DynJSException("LOOOL"); } if (instruction instanceof ResultInstruction) { storeResult(block, ((ResultInstruction) instruction).getResult()); } } private void emitCall(JiteClass jiteClass, CodeBlock block, Call instruction, HashMap<Label, LabelNode> jumpMap) { block.aload(EXECUTION_CONTEXT); emitOperand(block, instruction.getIdentifier()); // ref block.dup(); // ref ref block.dup(); // ref ref ref block.aload(EXECUTION_CONTEXT); // ref ref ref ec block.swap(); // ref ref ec ref block.invokestatic(p(Types.class), "getValue", sig(Object.class, ExecutionContext.class, Object.class)); // ref ref function block.checkcast(p(JSFunction.class)); // ref ref function block.swap(); // ref function ref block.invokestatic(p(Interpreter.class), "getThis", sig(Object.class, Object.class)); // ref function this Operand[] args = instruction.getArgs(); block.bipush(args.length); block.anewarray(p(Object.class)); for (int i = 0; i < args.length; i++) { block.dup(); block.bipush(i); Operand operand = args[i]; emitOperand(block, operand); block.arraystore(); } // ref function this args... block.invokevirtual(p(ExecutionContext.class), "call", sig(Object.class, Object.class, JSFunction.class, Object.class, Object[].class)); } private void emitSub(CodeBlock block, Sub instruction) { emitOperand(block, instruction.getLHS()); emitOperand(block, instruction.getRHS()); block.invokestatic(p(IRByteCodeCompiler.class), "sub", sig(Object.class, Object.class, Object.class)); } private void emitReceiveFunctionParameter(CodeBlock block, ReceiveFunctionParameter instruction, HashMap<Label, LabelNode> jumpMap) { block .aload(EXECUTION_CONTEXT) .pushInt(instruction.getIndex()) .invokevirtual(p(ExecutionContext.class), "getFunctionParameter", sig(Object.class, int.class)); } private void emitReturn(CodeBlock block, Return instruction, HashMap<Label, LabelNode> jumpMap) { emitOperand(block, instruction.getValue()); block.areturn(); } private void emitFunction(JiteClass jiteClass, HashMap<Label, LabelNode> jumpMap, CodeBlock block, DefineFunction instruction) { final FunctionScope functionScope = instruction.getScope(); final String[] parameterNames = functionScope.getParameterNames(); final CodeBlock fnBlock = new CodeBlock(); final List<BasicBlock> blocks = functionScope.prepareForCompilation(); for (BasicBlock bb : blocks) { for (Instruction fnInstr : bb.getInstructions()) { emitInstruction(jiteClass, jumpMap, fnBlock, fnInstr); } } if (!fnBlock.returns()) { fnBlock.aconst_null().areturn(); } final String methodName = nextSyntheticMethodName(functionScope); final String syntheticSignature = sig(Object.class, params(Object.class, ExecutionContext.class, Object.class, parameterNames.length)); jiteClass.defineMethod(methodName, Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, syntheticSignature, fnBlock); functionScope.setSyntheticMethodName(methodName); functionScope.setSyntheticSignature(syntheticSignature); } private void emitJump(CodeBlock block, Jump instruction, HashMap<Label, LabelNode> jumpMap) { block.go_to(jumpMap.get(instruction.getTarget())); } private void emitLT(CodeBlock block, LT instruction) { emitOperand(block, instruction.getArg1()); emitOperand(block, instruction.getArg2()); block.invokestatic(p(IRByteCodeCompiler.class), "lt", sig(Boolean.class, Object.class, Object.class)); } private void emitBEQ(CodeBlock block, BEQ instruction, Map<Label, LabelNode> jumpMap) { emitOperand(block, instruction.getArg1()); block.invokevirtual(p(Boolean.class), "booleanValue", sig(boolean.class)); emitOperand(block, instruction.getArg2()); final Label label = instruction.getTarget(); final LabelNode node = jumpMap.get(label); block.if_icmpeq(node); } private void emitOperand(CodeBlock block, Operand operand) { switch (operand.getType()) { case DYNAMIC_VAR: emitDynamicVar(block, (DynamicVariable) operand); break; case INTEGER: emitInteger(block, (IntegerNumber) operand); break; case TEMP_VAR: emitTempVar(block, (TemporaryVariable) operand); break; case LOCAL_VAR: emitLocalVar(block, (LocalVariable) operand); break; case BOOLEAN: emitBoolean(block, (BooleanLiteral) operand); break; case UNDEFINED: emitUndefined(block, (Undefined) operand); break; default: throw new DynJSException("loooool"); } } private void emitUndefined(CodeBlock block, Undefined operand) { block .getstatic(p(Types.class), "UNDEFINED", ci(Types.Undefined.class)); } public CodeBlock jsGetValue() { CodeBlock codeBlock = new CodeBlock() // IN: reference .aload(EXECUTION_CONTEXT) // reference context .swap() // context reference .invokestatic(p(Types.class), "getValue", sig(Object.class, ExecutionContext.class, Object.class)); return codeBlock; } private void emitDynamicVar(CodeBlock block, DynamicVariable operand) { block .aload(EXECUTION_CONTEXT) // context .ldc(operand.getName()) // context name .invokevirtual(p(ExecutionContext.class), "resolve", sig(Reference.class, String.class)) // reference .aload(EXECUTION_CONTEXT) // reference context .swap() // context reference .invokestatic(p(Types.class), "getValue", sig(Object.class, ExecutionContext.class, Object.class)); } private void emitBoolean(CodeBlock block, BooleanLiteral operand) { block.pushBoolean((Boolean) operand.retrieve(null, null)); } private void emitLocalVar(CodeBlock block, LocalVariable operand) { block .aload(EXECUTION_CONTEXT) .invokevirtual(p(ExecutionContext.class), "getVars", sig(VariableValues.class)) .pushInt(operand.getOffset()) .pushInt(operand.getDepth()) .invokevirtual(p(VariableValues.class), "getVar", sig(Object.class, int.class, int.class)); } private void emitTempVar(CodeBlock block, TemporaryVariable operand) { block.aload(getTempVarOffset() + operand.getOffset()); } private int getTempVarOffset() { return 2; } private void emitInteger(CodeBlock block, IntegerNumber operand) { block.pushInt((int) operand.getValue()); block.invokestatic(p(Integer.class), "valueOf", sig(Integer.class, int.class)); } private void emitAdd(CodeBlock block, Add instruction) { emitOperand(block, instruction.getLHS()); emitOperand(block, instruction.getRHS()); block.invokestatic(p(IRByteCodeCompiler.class), "add", sig(Object.class, Object.class, Object.class)); } private void emitCopy(CodeBlock block, Copy instruction) { emitOperand(block, instruction.getValue()); } private void storeResult(CodeBlock block, Variable result) { int offset = 0; switch (result.getType()) { case TEMP_VAR: offset = getTempVarOffset() + ((TemporaryVariable) result).getOffset(); block.astore(offset); break; case LOCAL_VAR: offset = ((LocalVariable) result).getOffset(); int depth = ((LocalVariable) result).getDepth(); block .aload(EXECUTION_CONTEXT) // ? EC .dup() // ? EC EC .invokevirtual(p(ExecutionContext.class), "getVars", sig(VariableValues.class)) // ? EC VV .dup2_x1() // EC VV ? EC VV .pop2() // EC VV ? .pushInt(offset) // EC VV ? offset .swap() // EC VV offset ? .pushInt(depth) // EC VV offset ? depth .swap() // EC VV offset depth ? .invokevirtual(p(VariableValues.class), "setVar", sig(void.class, int.class, int.class, Object.class)); break; } } private String nextSyntheticMethodName(FunctionScope scope) { final String[] parameterNames = scope.getParameterNames(); final StringBuilder builder = new StringBuilder(); for (int i = 0; i < parameterNames.length; i++) { builder.append(parameterNames[i]); builder.append("_"); } return "m_" + builder.toString() + "_" + methodCounter.getAndIncrement(); } private String nextCompiledFunctionName() { return "Function" + "_" + compiledFunctionCounter.getAndIncrement(); } public static Boolean lt(Object a, Object b) { Long la = a instanceof Long ? (Long) a : new Long(((Integer) a).longValue()); Long lb = b instanceof Long ? (Long) b : new Long(((Integer) b).longValue()); return la.compareTo(lb) == -1; } public static Object add(Object a, Object b) { Long la = a instanceof Long ? (Long) a : new Long(((Integer) a).longValue()); Long lb = b instanceof Long ? (Long) b : new Long(((Integer) b).longValue()); return la + lb; } public static Object sub(Object a, Object b) { Long la = a instanceof Long ? (Long) a : new Long(((Integer) a).longValue()); Long lb = b instanceof Long ? (Long) b : new Long(((Integer) b).longValue()); return la - lb; } public JSFunction compileFunction(ExecutionContext context) { final String methodName = nextSyntheticMethodName(scope); final String syntheticSignature = sig(Object.class, params(Object.class, ExecutionContext.class, Object.class, scope.getParameterNames().length)); scope.setSyntheticMethodName(methodName); scope.setSyntheticSignature(syntheticSignature); final JiteClass jiteClass = new JiteClass("org/dynjs/gen/" + nextCompiledFunctionName(), p(AbstractFunction.class), new String[]{p(JSFunction.class), p(JITCompiler.CompiledFunction.class)}); jiteClass.defineMethod("<init>", Opcodes.ACC_PUBLIC, sig(void.class, GlobalContext.class, LexicalEnvironment.class, boolean.class, String[].class), new CodeBlock() .aload(THIS) // this .aload(1) // this globalobject .aload(2) // this globalobject lexicalenvironment .iload(3) // this globalobject lexicalenvironment strict .aload(4) // this globalobject lexicalenvironment strict formalparamenters[] .invokespecial(p(AbstractFunction.class), "<init>", sig(void.class, GlobalContext.class, LexicalEnvironment.class, boolean.class, String[].class)) .voidreturn() ); final List<BasicBlock> blockList = this.blockList; final HashMap<Label, LabelNode> jumpMap = new HashMap<>(); CodeBlock block = new CodeBlock(); // first pass for gathering labels for (BasicBlock bb : blockList) { final Label label = bb.getLabel(); final LabelNode labelNode = new LabelNode(); jumpMap.put(label, labelNode); } // second pass for emitting for (BasicBlock bb : blockList) { block.label(jumpMap.get(bb.getLabel())); for (Instruction instruction : bb.getInstructions()) { emitInstruction(jiteClass, jumpMap, block, instruction); } } if (!block.returns()) { block.aconst_null().areturn(); } jiteClass.defineMethod("call", Opcodes.ACC_PUBLIC, sig(Object.class, ExecutionContext.class), block); final byte[] bytes = jiteClass.toBytes(); if (context.getConfig().isDebug()) { ClassReader reader = new ClassReader(bytes); CheckClassAdapter.verify(reader, context.getClassLoader(), true, new PrintWriter(System.out)); } final DynamicClassLoader loader = new DynamicClassLoader(); final Class<?> define = loader.define(jiteClass.getClassName().replace("/", "."), bytes); try { final Constructor<?> constructor = define.getDeclaredConstructor(GlobalContext.class, LexicalEnvironment.class, boolean.class, String[].class); final JSFunction function = (JSFunction) constructor.newInstance(context.getGlobalContext(), context.getLexicalEnvironment(), strict, scope.getParameterNames()); return function; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } }