/** * Creates a JMultiExpression from a set of JExpressionStatements, optionally terminated by a * JReturnStatement. If the method doesn't match this pattern, it returns <code>null</code>. * * <p>If a method has a non-void return statement and can be represented as a multi-expression, * the output of the multi-expression will be the return expression of the method. If the method * is void, the output of the multi-expression should be considered undefined. */ private JMultiExpression createMultiExpressionFromBody( JMethodBody body, boolean ignoringReturnValue) { JMultiExpression multi = new JMultiExpression(body.getSourceInfo()); CloneCalleeExpressionVisitor cloner = new CloneCalleeExpressionVisitor(); for (JStatement stmt : body.getStatements()) { if (stmt instanceof JExpressionStatement) { JExpressionStatement exprStmt = (JExpressionStatement) stmt; JExpression expr = exprStmt.getExpr(); JExpression clone = cloner.cloneExpression(expr); multi.addExpressions(clone); } else if (stmt instanceof JReturnStatement) { JReturnStatement returnStatement = (JReturnStatement) stmt; JExpression expr = returnStatement.getExpr(); if (expr != null) { if (!ignoringReturnValue || expr.hasSideEffects()) { JExpression clone = cloner.cloneExpression(expr); clone = maybeCast(clone, body.getMethod().getType()); multi.addExpressions(clone); } } // We hit an unconditional return; no need to evaluate anything else. break; } else { // Any other kind of statement won't be inlinable. return null; } } return multi; }
/** * Returns the ending statement for a method based on an expression. If the return type is void * then the ending statement just executes the expression otherwise it returns it. */ public static JStatement makeMethodEndStatement(JType returnType, JExpression expression) { // TODO(rluble): Check if something needs to be done here regarding boxing/unboxing/coercions // when one of the types of expression and returnType is a boxed type and the other a primitive // type or both are primitive of differnent coerceable types. Add the proper tests first. return returnType == JPrimitiveType.VOID ? expression.makeStatement() : expression.makeReturnStatement(); }
private JExpression maskUndefined(JExpression lhs) { assert ((JReferenceType) lhs.getType()).canBeNull(); JMethod maskMethod = program.getIndexedMethod("Cast.maskUndefined"); JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, maskMethod, lhs.getType()); lhsCall.addArg(lhs); return lhsCall; }
@Override public boolean hasSideEffects() { for (JExpression expression : expressions) { if (expression.hasSideEffects()) { return true; } } return false; }
/** * Insert an implicit cast if the types differ; it might get optimized out later, but in some * cases it will force correct math evaluation. */ private JExpression maybeCast(JExpression exp, JType targetType) { if (targetType instanceof JReferenceType) { assert exp.getType() instanceof JReferenceType; targetType = merge((JReferenceType) exp.getType(), (JReferenceType) targetType); } if (!program.typeOracle.castSucceedsTrivially(exp.getType(), targetType)) { exp = new JCastOperation(exp.getSourceInfo(), targetType, exp); } return exp; }
@Override public void endVisit(JDeclarationStatement x, Context ctx) { super.endVisit(x, ctx); lValues.pop(); // The variable may have been pruned. if (isVariablePruned(x.getVariableRef().getTarget())) { JExpression replacement = makeReplacementForAssignment(x.getSourceInfo(), x.getVariableRef(), x.getInitializer()); ctx.replaceMe(replacement.makeStatement()); } }
@Override JsLiteral translate(JExpression literal) { SourceInfo sourceInfo = literal.getSourceInfo(); long[] values = LongLib.getAsLongArray(((JLongLiteral) literal).getValue()); if (values.length == 1) { return new JsNumberLiteral(literal.getSourceInfo(), ((JLongLiteral) literal).getValue()); } JsObjectLiteral objectLiteral = new JsObjectLiteral(sourceInfo); objectLiteral.setInternable(); assert values.length == names.length; for (int i = 0; i < names.length; i++) { addPropertyToObject(sourceInfo, names[i], values[i], objectLiteral); } return objectLiteral; }
/** * Transform a call to a pruned instance method (or static impl) into a call to the null method, * which will be used to replace <code>x</code>. */ public static JMethodCall transformToNullMethodCall(JMethodCall x, JProgram program) { JExpression instance = x.getInstance(); List<JExpression> args = x.getArgs(); if (program.isStaticImpl(x.getTarget())) { instance = args.get(0); args = args.subList(1, args.size()); } else { /* * We assert that method must be non-static, otherwise it would have been * rescued. */ // assert !x.getTarget().isStatic(); /* * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to * never be rescured. This in turn causes invalid references to static * methods, which violates otherwise good assumptions about compiler * operation. * * TODO: Remove this when ControlFlowAnalyzer doesn't special-case * CLH.clinit(). */ if (x.getTarget().isStatic() && instance == null) { instance = program.getLiteralNull(); } } assert (instance != null); if (!instance.hasSideEffects()) { instance = program.getLiteralNull(); } JMethodCall newCall = new JMethodCall( x.getSourceInfo(), instance, program.getNullMethod(), primitiveTypeOrNullTypeOrArray(program, x.getType())); // Retain the original arguments, they will be evaluated for side effects. for (JExpression arg : args) { if (arg.hasSideEffects()) { newCall.addArg(arg); } } return newCall; }
@Override public void endVisit(JThrowStatement x, Context ctx) { assert jseType != null; JExpression expr = x.getExpr(); // Optimization: unwrap not needed if "new BlahException()" if (expr instanceof JNewInstance && !expr.getType().equals(jseType)) { return; } // Optimization: unwrap not needed if expression can never be JavaScriptException if (!program.typeOracle.canTheoreticallyCast((JReferenceType) expr.getType(), jseType)) { return; } // throw x; -> throw Exceptions.unwrap(x); ctx.replaceMe(createUnwrappedThrow(x)); }
/** * Creates a multi expression from a list of expressions, removing expressions that do not have * side effects if possible. */ public static JExpression createOptimizedMultiExpression( boolean ignoringResult, List<JExpression> expressions) { int numberOfExpressions = expressions.size(); JExpression result = expressions.get(numberOfExpressions - 1); numberOfExpressions = expressions.size(); if (numberOfExpressions == 0) { return new JMultiExpression(SourceOrigin.UNKNOWN); } expressions = Lists.newArrayList( Collections2.filter( expressions.subList(0, numberOfExpressions - 1), Predicates.and( Predicates.notNull(), new Predicate<JExpression>() { @Override public boolean apply(JExpression expression) { return expression.hasSideEffects(); } }))); if (result != null && (!ignoringResult || result.hasSideEffects())) { expressions.add(result); } if (expressions.size() == 1) { // Do not create a multi expression if it consists only of the result. return expressions.iterator().next(); } SourceInfo info = expressions.size() > 0 ? expressions.get(0).getSourceInfo() : SourceOrigin.UNKNOWN; return new JMultiExpression(info, expressions); }
/** * Installs the initial load sequence into AsyncFragmentLoader.BROWSER_LOADER. The initializer * looks like this: * * <pre> * AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{}); * </pre> * * The second argument (<code>new int[]</code>) gets replaced by an array corresponding to <code> * initialLoadSequence</code>. */ private static void installInitialLoadSequenceField( JProgram program, LinkedHashSet<JRunAsync> initialLoadSequence) { // Arg 1 is initialized in the source as "new int[]{}". JMethodCall call = ReplaceRunAsyncs.getBrowserLoaderConstructor(program); JExpression arg1 = call.getArgs().get(1); assert arg1 instanceof JNewArray; JArrayType arrayType = program.getTypeArray(JPrimitiveType.INT); assert ((JNewArray) arg1).getArrayType() == arrayType; List<JExpression> initializers = new ArrayList<JExpression>(initialLoadSequence.size()); // RunAsyncFramentIndex will later be replaced by the fragment the async is in. // TODO(rluble): this approach is not very clean, ideally the load sequence should be // installed AFTER code splitting when the fragment ids are known; rather than inserting // a placeholder in the AST and patching the ast later. for (JRunAsync runAsync : initialLoadSequence) { initializers.add( new JNumericEntry( call.getSourceInfo(), "RunAsyncFragmentIndex", runAsync.getRunAsyncId())); } JNewArray newArray = JNewArray.createArrayWithInitializers( arg1.getSourceInfo(), arrayType, Lists.newArrayList(initializers)); call.setArg(1, newArray); }
/** * Transform a reference to a pruned instance field into a reference to the null field, which will * be used to replace <code>x</code>. */ public static JFieldRef transformToNullFieldRef(JFieldRef x, JProgram program) { JExpression instance = x.getInstance(); /* * We assert that field must be non-static if it's an rvalue, otherwise it * would have been rescued. */ // assert !x.getField().isStatic(); /* * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to * never be rescured. This in turn causes invalid references to static * methods, which violates otherwise good assumptions about compiler * operation. * * TODO: Remove this when ControlFlowAnalyzer doesn't special-case * CLH.clinit(). */ if (x.getField().isStatic() && instance == null) { instance = program.getLiteralNull(); } assert instance != null; if (!instance.hasSideEffects()) { instance = program.getLiteralNull(); } JFieldRef fieldRef = new JFieldRef( x.getSourceInfo(), instance, program.getNullField(), x.getEnclosingType(), primitiveTypeOrNullTypeOrArray(program, x.getType())); return fieldRef; }
/** Returns an ast node representing the expression {@code expression != null}. */ public static JExpression createOptimizedNotNullComparison( JProgram program, SourceInfo info, JExpression expression) { JReferenceType type = (JReferenceType) expression.getType(); if (type.isNullType()) { return program.getLiteralBoolean(false); } if (!type.canBeNull()) { return createOptimizedMultiExpression(expression, program.getLiteralBoolean(true)); } return new JBinaryOperation( info, program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ, expression, program.getLiteralNull()); }
@Override public void endVisit(JBinaryOperation x, Context ctx) { JBinaryOperator op = x.getOp(); if (op != JBinaryOperator.EQ && op != JBinaryOperator.NEQ) { return; } JExpression lhs = x.getLhs(); JExpression rhs = x.getRhs(); JType lhsType = lhs.getType(); JType rhsType = rhs.getType(); if (!(lhsType instanceof JReferenceType)) { assert !(rhsType instanceof JReferenceType); return; } StringStatus lhsStatus = getStringStatus((JReferenceType) lhsType); StringStatus rhsStatus = getStringStatus((JReferenceType) rhsType); int strat = COMPARISON_STRAT[lhsStatus.getIndex()][rhsStatus.getIndex()]; switch (strat) { case STRAT_TRIPLE: { if (canBeNull(lhs) && canBeNull(rhs)) { /* * If it's possible for one side to be null and the other side * undefined, then mask both sides. */ lhs = maskUndefined(lhs); rhs = maskUndefined(rhs); } JBinaryOperation binOp = new JBinaryOperation(x.getSourceInfo(), x.getType(), x.getOp(), lhs, rhs); ctx.replaceMe(binOp); break; } case STRAT_DOUBLE: { boolean lhsNullLit = lhs == program.getLiteralNull(); boolean rhsNullLit = rhs == program.getLiteralNull(); if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING) || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) { /* * If either side is a null literal and the other is non-String, * replace with a null-check. */ String methodName; if (op == JBinaryOperator.EQ) { methodName = "Cast.isNull"; } else { methodName = "Cast.isNotNull"; } JMethod isNullMethod = program.getIndexedMethod(methodName); JMethodCall call = new JMethodCall(x.getSourceInfo(), null, isNullMethod); call.addArg(lhsNullLit ? rhs : lhs); ctx.replaceMe(call); } else { // Replace with a call to Cast.jsEquals, which does a == internally. String methodName; if (op == JBinaryOperator.EQ) { methodName = "Cast.jsEquals"; } else { methodName = "Cast.jsNotEquals"; } JMethod eqMethod = program.getIndexedMethod(methodName); JMethodCall call = new JMethodCall(x.getSourceInfo(), null, eqMethod); call.addArgs(lhs, rhs); ctx.replaceMe(call); } break; } } }
@Override JsLiteral translate(JExpression literal) { return new JsStringLiteral(literal.getSourceInfo(), ((JStringLiteral) literal).getValue()); }
@Override JsLiteral translate(JExpression literal) { return new JsNumberLiteral(literal.getSourceInfo(), ((JIntLiteral) literal).getValue()); }
public static void replaceMethodBody(JMethod method, JExpression returnValue) { JMethodBody body = (JMethodBody) method.getBody(); JBlock block = body.getBlock(); block.clear(); block.addStmt(returnValue.makeReturnStatement()); }
private static boolean canBeNull(JExpression x) { return ((JReferenceType) x.getType()).canBeNull(); }
// Arguments for pruned parameters will be pushed right into a multiexpression that will be // evaluated with the next arg, e.g. m(arg1, (prunnedArg2, prunnedArg3, arg4)). private void maybeReplaceForPrunedParameters(JMethodCall x, Context ctx) { if (!priorParametersByMethod.containsKey(x.getTarget())) { // No parameter was pruned. return; } JMethodCall replacementCall = x.cloneWithoutParameters(); assert !x.getTarget().canBePolymorphic(); List<JParameter> originalParams = priorParametersByMethod.get(x.getTarget()); // The method and the call agree in the number of parameters. assert originalParams.size() == x.getArgs().size(); // Traverse the call arguments left to right. SourceInfo sourceInfo = x.getSourceInfo(); JMultiExpression unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo); List<JExpression> args = x.getArgs(); for (int currentArgumentIndex = 0; currentArgumentIndex < args.size(); ++currentArgumentIndex) { JExpression arg = args.get(currentArgumentIndex); // If the parameter was not pruned . if (referencedNonTypes.contains(originalParams.get(currentArgumentIndex))) { // Add the current argument to the list of unevaluated arguments and pass the multi // expression to the call. unevaluatedArgumentsForPrunedParameters.addExpressions(arg); replacementCall.addArg(unevaluatedArgumentsForPrunedParameters); // Reset the accumulating multi expression. unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo); } else if (arg.hasSideEffects()) { // If the argument was pruned and has sideffects accumulate it; otherwise discard. unevaluatedArgumentsForPrunedParameters.addExpressions(arg); } } if (unevaluatedArgumentsForPrunedParameters.isEmpty()) { // We are done, all (side effectful) parameters have been evaluated. ctx.replaceMe(replacementCall); return; } // If the last few parameters where pruned, we need to evaluate the (side effectful) arguments // for those parameters. if (replacementCall.getArgs().isEmpty()) { // All parameters have been pruned, replace by (prunedArg1, ..., prunedArgn, m()). unevaluatedArgumentsForPrunedParameters.addExpressions(replacementCall); ctx.replaceMe(unevaluatedArgumentsForPrunedParameters); return; } // Some parameters have been pruned from the end, replace by // m(arg1,..., (lastArg = lastUnprunedArg, remainingArgs, lastArg)) JExpression lastArg = Iterables.getLast(replacementCall.getArgs()); JLocal tempVar = createTempLocal( sourceInfo, Iterables.getLast(Iterables.filter(originalParams, Predicates.in(referencedNonTypes))) .getType()); unevaluatedArgumentsForPrunedParameters.addExpressions( 0, JProgram.createAssignment( lastArg.getSourceInfo(), new JLocalRef(sourceInfo, tempVar), lastArg)); unevaluatedArgumentsForPrunedParameters.addExpressions(new JLocalRef(sourceInfo, tempVar)); replacementCall.setArg( replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters); ctx.replaceMe(replacementCall); }