@Override public void endVisit(JsniFieldRef x, Context ctx) { if (isPruned(x.getField())) { String ident = x.getIdent(); JField nullField = program.getNullField(); JsniFieldRef nullFieldRef = new JsniFieldRef( x.getSourceInfo(), ident, nullField, x.getEnclosingType(), x.isLvalue()); ctx.replaceMe(nullFieldRef); } }
@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 public void endVisit(JsniMethodRef x, Context ctx) { // Redirect JSNI refs to uninstantiable types to the null method. if (isPruned(x.getTarget())) { String ident = x.getIdent(); JMethod nullMethod = program.getNullMethod(); JsniMethodRef nullMethodRef = new JsniMethodRef(x.getSourceInfo(), ident, nullMethod, program.getJavaScriptObject()); ctx.replaceMe(nullMethodRef); } }
@Override public void endVisit(JReturnStatement x, Context ctx) { info(x); JReturnStatement toReturn = new JReturnStatement( x.getSourceInfo(), new JMethodCall( x.getSourceInfo(), new JThisRef(x.getSourceInfo(), enumType), enumObfuscatedName)); ctx.replaceMe(toReturn); }
@Override public void endVisit(JFieldRef x, Context ctx) { // Handle l-values at a higher level. if (lValues.peek() == x) { return; } if (isPruned(x.getField())) { // The field is gone; replace x by a null field reference. JFieldRef fieldRef = transformToNullFieldRef(x, program); ctx.replaceMe(fieldRef); } }
@Override public void endVisit(JParameterRef x, Context ctx) { int paramIndex = methodCall.getTarget().getParams().indexOf(x.getParameter()); assert paramIndex != -1; // Replace with a cloned call argument. CloneExpressionVisitor cloner = new CloneExpressionVisitor(); JExpression arg = methodCall.getArgs().get(paramIndex); JExpression clone = cloner.cloneExpression(arg); clone = maybeCast(clone, x.getType()); ctx.replaceMe(clone); }
@Override public void endVisit(JMethodCall x, Context ctx) { JMethod method = x.getTarget(); // Is the method pruned entirely? if (isPruned(method)) { /* * We assert that method must be non-static, otherwise it would have * been rescued. */ ctx.replaceMe(transformToNullMethodCall(x, program)); return; } maybeReplaceForPrunedParameters(x, ctx); }
@Override public void endVisit(JLocalRef x, Context ctx) { JLocal originalLocal = x.getLocal(); JLocal newLocal = newLocalsByOriginalLocal.get(originalLocal); if (newLocal == null) { newLocal = JProgram.createLocal( originalLocal.getSourceInfo(), originalLocal.getName(), originalLocal.getType(), originalLocal.isFinal(), methodBody); newLocalsByOriginalLocal.put(originalLocal, newLocal); } ctx.replaceMe(new JLocalRef(x.getSourceInfo(), newLocal)); }
@Override public void endVisit(JNameOf x, Context ctx) { HasName node = x.getNode(); boolean pruned; if (node instanceof JField) { pruned = isPruned((JField) node); } else if (node instanceof JMethod) { pruned = isPruned((JMethod) node); } else if (node instanceof JReferenceType) { pruned = !program.typeOracle.isInstantiatedType((JReferenceType) node); } else { throw new InternalCompilerException("Unhandled JNameOf node: " + node); } if (pruned) { ctx.replaceMe(program.getLiteralNull()); } }
@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)); }
@Override public void endVisit(JBinaryOperation x, Context ctx) { if (x.getOp() != JBinaryOperator.ASG) { return; } // The LHS of assignments may have been pruned. lValues.pop(); JExpression lhs = x.getLhs(); if (!(lhs instanceof JVariableRef)) { return; } JVariableRef variableRef = (JVariableRef) lhs; if (isVariablePruned(variableRef.getTarget())) { // TODO: better null tracking; we might be missing some NPEs here. JExpression replacement = makeReplacementForAssignment(x.getSourceInfo(), variableRef, x.getRhs()); ctx.replaceMe(replacement); } }
@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; } } }
/** * Inline a call to an expression. Returns {@code InlineResult.BLACKLIST} if the method is * deemed not inlineable regardless of call site; {@code InlineResult.DO_NOT_BLACKLIST} * otherwise. */ private InlineResult tryInlineBody( JMethodCall x, Context ctx, List<JExpression> bodyAsExpressionList, boolean ignoringReturn) { if (isTooComplexToInline(bodyAsExpressionList, ignoringReturn)) { return InlineResult.BLACKLIST; } // Do not inline anything that modifies one of its params. ExpressionAnalyzer targetAnalyzer = new ExpressionAnalyzer(); targetAnalyzer.accept(bodyAsExpressionList); if (targetAnalyzer.hasAssignmentToParameter()) { return InlineResult.BLACKLIST; } // Make sure the expression we're about to inline doesn't include a call // to the target method! RecursionCheckVisitor recursionCheckVisitor = new RecursionCheckVisitor(x.getTarget()); recursionCheckVisitor.accept(bodyAsExpressionList); if (recursionCheckVisitor.isRecursive()) { return InlineResult.BLACKLIST; } /* * After this point, it's possible that the method might be inlinable at * some call sites, depending on its arguments. From here on return 'true' * as the method might be inlinable elsewhere. */ /* * There are a different number of parameters than args - this is likely a * result of parameter pruning. Don't consider this call site a candidate. * * TODO: would this be possible in the trivial delegation case? */ if (x.getTarget().getParams().size() != x.getArgs().size()) { // Could not inline this call but the method might be inlineable at a different call site. return InlineResult.DO_NOT_BLACKLIST; } // Run the order check. This verifies that all the parameters are // referenced once and only once, not within a conditionally-executing // expression and before any tricky target expressions, such as: // - assignments to any variable // - expressions that throw exceptions // - field references /* * Ensure correct evaluation order or params relative to each other and to * other expressions. */ OrderVisitor orderVisitor = new OrderVisitor(x.getTarget().getParams()); orderVisitor.accept(bodyAsExpressionList); switch (orderVisitor.checkResults()) { case NO_REFERENCES: /* * A method that doesn't touch any parameters is trivially inlinable (this * covers the empty method case) */ if (!x.hasSideEffects()) { markCallsAsSideEffectFree(bodyAsExpressionList); } new LocalVariableExtruder(getCurrentMethod()).accept(bodyAsExpressionList); List<JExpression> expressions = expressionsIncludingArgs(x); expressions.addAll(bodyAsExpressionList); ctx.replaceMe(JjsUtils.createOptimizedMultiExpression(ignoringReturn, expressions)); return InlineResult.DO_NOT_BLACKLIST; case FAILS: /* * We can still inline in the case where all of the actual arguments are * "safe". They must have no side effects, and also have values which * could not be affected by the execution of any code within the callee. */ for (JExpression arg : x.getArgs()) { ExpressionAnalyzer argAnalyzer = new ExpressionAnalyzer(); argAnalyzer.accept(arg); if (argAnalyzer.hasAssignment() || argAnalyzer.accessesField() || argAnalyzer.createsObject() || argAnalyzer.canThrowException()) { /* * This argument evaluation could affect or be affected by the * callee so we cannot inline here. */ // Could not inline this call but the method is potentially inlineable. return InlineResult.DO_NOT_BLACKLIST; } } // Fall through! case CORRECT_ORDER: default: if (!x.hasSideEffects()) { markCallsAsSideEffectFree(bodyAsExpressionList); } new LocalVariableExtruder(getCurrentMethod()).accept(bodyAsExpressionList); // Replace all params in the target expression with the actual arguments. ParameterReplacer replacer = new ParameterReplacer(x); replacer.accept(bodyAsExpressionList); bodyAsExpressionList.add(0, x.getInstance()); bodyAsExpressionList.add(1, createClinitCall(x)); ctx.replaceMe( JjsUtils.createOptimizedMultiExpression(ignoringReturn, bodyAsExpressionList)); return InlineResult.DO_NOT_BLACKLIST; } }
@Override public void endVisit(JParameterRef x, Context ctx) { if (x.getParameter() == parameter) { ctx.replaceMe(cloner.cloneExpression(expression)); } }
/** * Replace the current expression with a given multi-expression and mark the method as modified. * The dead-code elimination pass will optimize this if necessary. */ private void replaceWithMulti(Context ctx, JMultiExpression multi) { ctx.replaceMe(multi); modifiedMethods.add(currentMethod); }
// 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); }
@Override public void endVisit(JRuntimeTypeReference x, Context ctx) { if (!program.typeOracle.isInstantiatedType(x.getReferredType())) { ctx.replaceMe(program.getLiteralNull()); } }