package org.dynjs.codegen; import static me.qmx.jitescript.util.CodegenUtils.*; import java.io.PrintStream; import me.qmx.jitescript.CodeBlock; import org.dynjs.compiler.CompilationContext; import org.dynjs.compiler.JSCompiler; import org.dynjs.exception.ThrowException; import org.dynjs.parser.CodeVisitor; import org.dynjs.parser.Statement; import org.dynjs.parser.ast.AdditiveExpression; import org.dynjs.parser.ast.BlockStatement; import org.dynjs.parser.js.Position; import org.dynjs.runtime.BasicBlock; import org.dynjs.runtime.BlockManager; import org.dynjs.runtime.BlockManager.Entry; import org.dynjs.runtime.Completion; import org.dynjs.runtime.Completion.Type; import org.dynjs.runtime.ExecutionContext; import org.dynjs.runtime.JSFunction; import org.dynjs.runtime.JSObject; import org.dynjs.runtime.Reference; import org.dynjs.runtime.Types; import org.dynjs.runtime.interp.InterpretedBasicBlock; import org.dynjs.runtime.interp.InterpretingVisitorFactory; import me.qmx.jitescript.internal.org.objectweb.asm.tree.LabelNode; public abstract class CodeGeneratingVisitor extends CodeBlock implements CodeVisitor { public static interface Arities { int THIS = 0; int EXECUTION_CONTEXT = 1; int COMPLETION = 2; } private InterpretingVisitorFactory interpFactory; private BlockManager blockManager; public CodeGeneratingVisitor(InterpretingVisitorFactory interpFactory, BlockManager blockManager) { this.interpFactory = interpFactory; this.blockManager = blockManager; } public BlockManager getBlockManager() { return this.blockManager; } protected void emitDebug(String message) { ldc(message); aprintln(); pop(); } protected void emitTop() { aprintln(); } public CodeBlock aprintln() { dup(); getstatic(p(System.class), "err", ci(PrintStream.class)); swap(); invokevirtual(p(PrintStream.class), "println", sig(void.class, params(Object.class))); return this; } @Override public Object visit(Object context, AdditiveExpression expr, boolean strict) { if (expr.getOp().equals("+")) { return visitPlus((ExecutionContext) context, expr, strict); } else { return visitMinus((ExecutionContext) context, expr, strict); } } public abstract Object visitPlus(ExecutionContext context, AdditiveExpression expr, boolean strict); public abstract Object visitMinus(ExecutionContext context, AdditiveExpression expr, boolean strict); public CodeBlock jsCheckObjectCoercible(final String debug) { CodeBlock codeBlock = new CodeBlock() // IN: obj .dup() // obj obj .aload(Arities.EXECUTION_CONTEXT) // obj obj context .swap(); // obj context obj if (debug != null) { codeBlock.ldc(debug); } else { codeBlock.aconst_null(); } codeBlock.invokestatic(p(Types.class), "checkObjectCoercible", sig(void.class, ExecutionContext.class, Object.class, String.class)); // obj return codeBlock; } public CodeBlock jsResolve(final String identifier) { return new CodeBlock() // <EMPTY> .aload(Arities.EXECUTION_CONTEXT) .ldc(identifier) .invokevirtual(p(ExecutionContext.class), "resolve", sig(Reference.class, String.class)); // reference } public CodeBlock jsPushUndefined() { return new CodeBlock() .getstatic(p(Types.class), "UNDEFINED", ci(Types.Undefined.class)); } public CodeBlock jsPushNull() { return new CodeBlock() .getstatic(p(Types.class), "NULL", ci(Types.Null.class)); } public CodeBlock jsToPrimitive() { return new CodeBlock() // IN: obj preferredType .aload(Arities.EXECUTION_CONTEXT) // obj preferredType context .dup_x2() // context obj preferredType context .pop() // context obj preferredType .invokestatic(p(Types.class), "toPrimitive", sig(Object.class, ExecutionContext.class, Object.class, String.class)); // obj } public CodeBlock jsToNumber() { return new CodeBlock() // IN obj .aload(Arities.EXECUTION_CONTEXT) // obj context .swap() // context obj .invokestatic(p(Types.class), "toNumber", sig(Number.class, ExecutionContext.class, Object.class)); // obj } public CodeBlock jsToBoolean() { return new CodeBlock() // IN obj .invokestatic(p(Types.class), "toBoolean", sig(Boolean.class, Object.class)); // obj } public CodeBlock jsToInt32() { return new CodeBlock() // IN obj .aload(Arities.EXECUTION_CONTEXT) // obj context .swap() // context obj .invokestatic(p(Types.class), "toInt32", sig(Long.class, ExecutionContext.class, Object.class)); // obj } public CodeBlock jsToUint32() { return new CodeBlock() // IN obj .aload(Arities.EXECUTION_CONTEXT) // obj context .swap() // context obj .invokestatic(p(Types.class), "toUint32", sig(Long.class, ExecutionContext.class, Object.class)); // obj } public CodeBlock jsToObject() { return new CodeBlock() // IN obj .aload(Arities.EXECUTION_CONTEXT) // obj context .swap() // context object .invokestatic(p(Types.class), "toObject", sig(JSObject.class, ExecutionContext.class, Object.class)); // obj } public CodeBlock jsGetValue() { return jsGetValue(null); } public abstract CodeBlock jsGetValue(final Class<?> throwIfNot); public CodeBlock jsGetBase() { return new CodeBlock() // IN: reference // reference .invokevirtual(p(Reference.class), "getBase", sig(Object.class)); // value } public CodeBlock jsToString() { return new CodeBlock() // IN: obj .aload(Arities.EXECUTION_CONTEXT) // obj context .swap() // context obj .invokestatic(p(Types.class), "toString", sig(String.class, ExecutionContext.class, Object.class)); } public CodeBlock jsCreatePropertyReference() { return new CodeBlock() // IN: context obj identifier .invokevirtual(p(ExecutionContext.class), "createPropertyReference", sig(Reference.class, Object.class, String.class)); } public CodeBlock jsThrowTypeError(final String message) { return new CodeBlock() .newobj(p(ThrowException.class)) // obj .dup() // obj obj .aload(Arities.EXECUTION_CONTEXT) // obj obj context .ldc(message) // obj obj context message .invokevirtual(p(ExecutionContext.class), "createTypeError", sig(JSObject.class, String.class)) // obj obj ex .aload(Arities.EXECUTION_CONTEXT) // obj obj ex context .swap() // obj obj context ex .invokespecial(p(ThrowException.class), "<init>", sig(void.class, ExecutionContext.class, Object.class)) // obj .athrow(); } public CodeBlock jsThrowReferenceError(final String message) { return new CodeBlock() .newobj(p(ThrowException.class)) // obj .dup() // obj obj .aload(Arities.EXECUTION_CONTEXT) // obj obj context .ldc(message) // obj obj context message .invokevirtual(p(ExecutionContext.class), "createReferenceError", sig(JSObject.class, String.class)) // obj obj ex .aload(Arities.EXECUTION_CONTEXT) // obj obj ex context .swap() // obj obj context ex .invokespecial(p(ThrowException.class), "<init>", sig(void.class, ExecutionContext.class, Object.class)) // obj .athrow(); } public CodeBlock jsThrowSyntaxError(final String message) { return new CodeBlock() .newobj(p(ThrowException.class)) // obj .dup() // obj obj .aload(Arities.EXECUTION_CONTEXT) // obj obj context .ldc(message) // obj obj context message .invokevirtual(p(ExecutionContext.class), "createSyntaxError", sig(JSObject.class, String.class)) // obj obj ex .aload(Arities.EXECUTION_CONTEXT) // obj obj ex context .swap() // obj obj context ex .invokespecial(p(ThrowException.class), "<init>", sig(void.class, ExecutionContext.class, Object.class)) // obj .athrow(); } public CodeBlock ifEitherIsDouble(final LabelNode target) { // IN: Number Number return new CodeBlock() .checkcast(p(Number.class)) .swap() // val(rhs) Number(lhs) .checkcast(p(Number.class)) .swap() // Number(lhs) Number(rhs) .dup() // Number(lhs) Number(rhs) Number(rhs) .instance_of(p(Double.class)) // Number(lhs) Number(rhs) bool .iftrue(target) // Number(lhs) Number(rhs) .swap() // Number(rhs) Number(lhs) .dup_x1() // Number(lhs) Number(rhs) Number(lhs) .instance_of(p(Double.class)) // Number(lhs) Number(rhs) bool .iftrue(target); // Number(lhs) Number(rhs) } public CodeBlock ifEitherIsNaN(final LabelNode target) { // IN: Number Number return new CodeBlock() // Number(x) Number(y) .checkcast(p(Number.class)) // val(x) Number(y) .dup_x1() // Number(y) val(x) Number(y) .swap() // Number(y) Number(y) val(x) .checkcast(p(Number.class)) // Number(y) Number(y) Number(x) .dup_x2() // Number(x) Number(y) Number(y) Number(x) .swap() // Number(x) Number(y) Number(x) Number(y) .invokestatic(p(CodeGeneratingVisitor.class), "isEitherNaN", sig(boolean.class, Number.class, Number.class)) // Number(x) Number(y) bool .iftrue(target); // Number(x) Number(y) } public CodeBlock ifTopIsZero(final LabelNode target) { // IN: Number return new CodeBlock() // Number .checkcast(p(Number.class)) // Number .dup() // Number Number .invokestatic(p(CodeGeneratingVisitor.class), "isZero", sig(boolean.class, Number.class)) // Number bool .iftrue(target); // Number } public static boolean isEitherNaN(Number lhs, Number rhs) { return (Double.isNaN(lhs.doubleValue()) || Double.isNaN(rhs.doubleValue())); } public static boolean isZero(Number num) { return (num.doubleValue() == 0.0); } public CodeBlock ifBothAreString(final LabelNode target) { LabelNode end = new LabelNode(); return new CodeBlock() // IN: obj(lhs) obj(rhs) .dup() // obj(lhs) obj(rhs) obj(rhs) .instance_of(p(String.class)) // obj(lhs) obj(rhs) bool(rhs) .iffalse(end) // obj(lhs) obj(rhs) .swap() // obj(rhs) obj(lhs) .dup_x1() // obj(lhs) obj(rhs) obj(lhs) .instance_of(p(String.class)) // obj(lhs) obj(rhs) bool(lhs) .iftrue(target) // obj(lhs) obj(rhs) .label(end); // obj(lhs) obj(rhs) } public CodeBlock convertTopTwoToPrimitiveLongs() { return new CodeBlock() // IN: Number Number .invokevirtual(p(Number.class), "longValue", sig(long.class)) // Number(lhs) long(rhs) .dup2_x1() // long(rhs) Number(lhs) long(rhs) .pop2() // long(rhs) Number(lhs) .invokevirtual(p(Number.class), "longValue", sig(long.class)) // long(rhs) long(lhs) .dup2_x2() // long(lhs) long(rhs) long(lhs); .pop2(); // long(lhs) long(rhs) } public CodeBlock convertTopToLong() { return new CodeBlock() // IN: int .invokestatic(p(Long.class), "valueOf", sig(Long.class, long.class)); } public CodeBlock convertTopToInteger() { return new CodeBlock() // IN: int .invokestatic(p(Integer.class), "valueOf", sig(Integer.class, int.class)); } public CodeBlock convertTopTwoToPrimitiveDoubles() { return new CodeBlock() // IN Number Number .checkcast(p(Number.class)) .swap() .checkcast(p(Number.class)) .swap() .invokevirtual(p(Number.class), "doubleValue", sig(double.class)) // Number(lhs) double(rhs) .dup2_x1() // double(rhs) Number(lhs) double(rhs); .pop2() // double(rhs) Number(lhs) .invokevirtual(p(Number.class), "doubleValue", sig(double.class)) // double(rhs) double(lhs) .swap2(); // OUT double double } public CodeBlock convertTopToDouble() { return new CodeBlock() // IN: int .invokestatic(p(Double.class), "valueOf", sig(Double.class, double.class)); } // ---------------------------------------- public CodeBlock jsCompletionValue() { return new CodeBlock() // IN completion .getfield(p(Completion.class), "value", ci(Object.class)); // value } public CodeBlock handleCompletion( final LabelNode normalTarget, final LabelNode breakTarget, final LabelNode continueTarget, final LabelNode returnTarget) { return new CodeBlock() // IN: completion .append(jsCompletionType()) .lookupswitch(normalTarget, new int[] { Type.NORMAL.ordinal(), Type.BREAK.ordinal(), Type.CONTINUE.ordinal(), Type.RETURN.ordinal() }, new LabelNode[] { normalTarget, breakTarget, continueTarget, returnTarget }); } public CodeBlock jsCompletionTarget() { return new CodeBlock() // IN completion .getfield(p(Completion.class), "target", ci(String.class)); // value } public CodeBlock jsCompletionType() { return new CodeBlock() // IN completion .getfield(p(Completion.class), "type", ci(Completion.Type.class)) // type .invokevirtual(p(Completion.Type.class), "ordinal", sig(int.class)); } public void breakCompletion(final String target) { // <EMPTY> if (target == null) { aconst_null(); } else { ldc(target); } // target invokestatic(p(Completion.class), "createBreak", sig(Completion.class, String.class)); // completion } public void continueCompletion(final String target) { // <EMPTY> if (target == null) { aconst_null(); } else { ldc(target); } // target invokestatic(p(Completion.class), "createContinue", sig(Completion.class, String.class)); // completion } public void normalCompletion() { invokestatic(p(Completion.class), "createNormal", sig(Completion.class)); } public void normalCompletionWithValue() { // IN: val invokestatic(p(Completion.class), "createNormal", sig(Completion.class, Object.class)); } public void returnCompletion() { invokestatic(p(Completion.class), "createReturn", sig(Completion.class, Object.class)); } public void convertToNormalCompletion() { // IN: completion dup(); // completion completion getstatic(p(Completion.Type.class), "NORMAL", ci(Type.class)); // completion completion NORMAL putfield(p(Completion.class), "type", ci(Type.class)); // completion } public static void injectLineNumber(CodeBlock block, Statement statement) { Position position = statement.getPosition(); if (position != null) { LabelNode lineLabel = new LabelNode(); block.line(position.getLine(), lineLabel); block.label(lineLabel); } } public void invokeCompiledStatementBlock(final String grist, final Statement block, final boolean strict) { compiledStatementBlock(grist, block, strict); // basic-block aload(Arities.EXECUTION_CONTEXT); // basic-block context // invokevirtual( p( BasicBlock.class ), "call", sig( Completion.class, ExecutionContext.class ) ); invokeinterface(p(BasicBlock.class), "call", sig(Completion.class, ExecutionContext.class)); // completion } public void compiledStatementBlock(final String grist, final Statement block, final boolean strict) { int statementNumber = block.getStatementNumber(); Entry entry = blockManager.retrieve(statementNumber); // Stash statement if required if (entry.statement == null) { entry.statement = block; } // ---------------------------------------- // ---------------------------------------- aload(Arities.EXECUTION_CONTEXT); // context ldc(statementNumber); // context statement-num invokevirtual(p(ExecutionContext.class), "retrieveBlockEntry", sig(Entry.class, int.class)); // entry aload(Arities.EXECUTION_CONTEXT); // entry context invokevirtual(p(ExecutionContext.class), "getCompiler", sig(JSCompiler.class)); // entry compiler swap(); // compiler entry aload(Arities.EXECUTION_CONTEXT); // compiler entry context swap(); // compiler context entry ldc(grist); // compiler context entry grist swap(); // compiler context grist entry getfield(p(Entry.class), "statement", ci(Statement.class)); // compiler context grist statement if ( strict ) { iconst_1(); } else { iconst_0(); } // compiler context grist statement strict invokevirtual(p(JSCompiler.class), "compileBasicBlock", sig(BasicBlock.class, ExecutionContext.class, String.class, Statement.class, boolean.class)); // basic-block } public void compiledFunction(final String identifier, final String[] formalParams, final Statement block, final boolean strict) { int statementNumber = block.getStatementNumber(); Entry entry = blockManager.retrieve(statementNumber); // Stash statement if required if (entry.statement == null) { entry.statement = block; } // ---------------------------------------- // ---------------------------------------- aload(Arities.EXECUTION_CONTEXT); // context ldc(statementNumber); // context statement-number invokevirtual(p(ExecutionContext.class), "retrieveBlockEntry", sig(Entry.class, int.class)); // entry aload(Arities.EXECUTION_CONTEXT); // entry context invokevirtual(p(ExecutionContext.class), "getCompiler", sig(JSCompiler.class)); // entry compiler swap(); // compiler entry getfield(p(Entry.class), "statement", ci(Statement.class)); // compiler statement aload(Arities.EXECUTION_CONTEXT); // compiler statement context swap(); // compiler context statement if (identifier != null) { ldc(identifier); } else { aconst_null(); } // compiler context statement identifier swap(); // compiler context identifier statement checkcast(p(BlockStatement.class)); bipush(formalParams.length); // compiler context identifier statement params-en anewarray(p(String.class)); // compiler context identifier statement params for (int i = 0; i < formalParams.length; ++i) { dup(); bipush(i); ldc(formalParams[i]); aastore(); } // compiler context identifier statement params swap(); // compiler context identifier params statement if (strict) { iconst_1(); } else { iconst_0(); } // compiler context identifer params statement bool invokevirtual(p(JSCompiler.class), "compileFunction", sig(JSFunction.class, CompilationContext.class, String.class, String[].class, Statement.class, boolean.class)); // fn } void interpretedStatement(Statement statement, boolean strict) { Entry entry = getBlockManager().retrieve(statement.getStatementNumber()); InterpretedBasicBlock interpreted = new InterpretedBasicBlock(this.interpFactory, statement, strict); entry.setCompiled(interpreted); aload(Arities.EXECUTION_CONTEXT); ldc(statement.getStatementNumber()); invokevirtual(p(ExecutionContext.class), "retrieveBlockEntry", sig(Entry.class, int.class)); invokevirtual(p(Entry.class), "getCompiled", sig(BasicBlock.class)); // object aload(Arities.EXECUTION_CONTEXT); // obj context invokeinterface(p(BasicBlock.class), "call", sig(Completion.class, ExecutionContext.class)); // completion } }