0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev.js;
0017:
0018: import com.google.gwt.dev.jjs.InternalCompilerException;
0019: import com.google.gwt.dev.js.ast.JsArrayAccess;
0020: import com.google.gwt.dev.js.ast.JsArrayLiteral;
0021: import com.google.gwt.dev.js.ast.JsBinaryOperation;
0022: import com.google.gwt.dev.js.ast.JsBinaryOperator;
0023: import com.google.gwt.dev.js.ast.JsBlock;
0024: import com.google.gwt.dev.js.ast.JsBooleanLiteral;
0025: import com.google.gwt.dev.js.ast.JsCase;
0026: import com.google.gwt.dev.js.ast.JsConditional;
0027: import com.google.gwt.dev.js.ast.JsContext;
0028: import com.google.gwt.dev.js.ast.JsDecimalLiteral;
0029: import com.google.gwt.dev.js.ast.JsDefault;
0030: import com.google.gwt.dev.js.ast.JsExprStmt;
0031: import com.google.gwt.dev.js.ast.JsExpression;
0032: import com.google.gwt.dev.js.ast.JsFor;
0033: import com.google.gwt.dev.js.ast.JsForIn;
0034: import com.google.gwt.dev.js.ast.JsFunction;
0035: import com.google.gwt.dev.js.ast.JsIf;
0036: import com.google.gwt.dev.js.ast.JsIntegralLiteral;
0037: import com.google.gwt.dev.js.ast.JsInvocation;
0038: import com.google.gwt.dev.js.ast.JsModVisitor;
0039: import com.google.gwt.dev.js.ast.JsName;
0040: import com.google.gwt.dev.js.ast.JsNameRef;
0041: import com.google.gwt.dev.js.ast.JsNew;
0042: import com.google.gwt.dev.js.ast.JsNode;
0043: import com.google.gwt.dev.js.ast.JsNullLiteral;
0044: import com.google.gwt.dev.js.ast.JsObjectLiteral;
0045: import com.google.gwt.dev.js.ast.JsParameter;
0046: import com.google.gwt.dev.js.ast.JsPostfixOperation;
0047: import com.google.gwt.dev.js.ast.JsPrefixOperation;
0048: import com.google.gwt.dev.js.ast.JsProgram;
0049: import com.google.gwt.dev.js.ast.JsRegExp;
0050: import com.google.gwt.dev.js.ast.JsReturn;
0051: import com.google.gwt.dev.js.ast.JsScope;
0052: import com.google.gwt.dev.js.ast.JsStatement;
0053: import com.google.gwt.dev.js.ast.JsStringLiteral;
0054: import com.google.gwt.dev.js.ast.JsSwitchMember;
0055: import com.google.gwt.dev.js.ast.JsThisRef;
0056: import com.google.gwt.dev.js.ast.JsThrow;
0057: import com.google.gwt.dev.js.ast.JsUnaryOperation;
0058: import com.google.gwt.dev.js.ast.JsVisitable;
0059: import com.google.gwt.dev.js.ast.JsVisitor;
0060: import com.google.gwt.dev.js.ast.JsWhile;
0061:
0062: import java.util.ArrayList;
0063: import java.util.Arrays;
0064: import java.util.Collection;
0065: import java.util.HashMap;
0066: import java.util.HashSet;
0067: import java.util.IdentityHashMap;
0068: import java.util.List;
0069: import java.util.ListIterator;
0070: import java.util.Map;
0071: import java.util.Set;
0072: import java.util.Stack;
0073:
0074: /**
0075: * Perform inlining optimizations on the JavaScript AST.
0076: */
0077: public class JsInliner {
0078:
0079: /**
0080: * Make comma binary operations left-nested since commas are naturally
0081: * left-associative. We will define the comma-normal form such that a comma
0082: * expression should never have a comma expression as its RHS. This has a nice
0083: * side-effect of minimizing the number of generated parentheses.
0084: *
0085: * <pre>
0086: * (X, b) is unchanged
0087: * (X, (b, c) becomes ((X, b), c)
0088: * (X, ((b, c), d)) becomes (((X, b), c), d)
0089: * </pre>
0090: */
0091: private static class CommaNormalizer extends JsModVisitor {
0092:
0093: /**
0094: * Returns an expression as a JsBinaryOperation if it is a comma expression.
0095: */
0096: private static JsBinaryOperation isComma(JsExpression x) {
0097: if (!(x instanceof JsBinaryOperation)) {
0098: return null;
0099: }
0100: JsBinaryOperation op = (JsBinaryOperation) x;
0101:
0102: return op.getOperator().equals(JsBinaryOperator.COMMA) ? op
0103: : null;
0104: }
0105:
0106: @Override
0107: public void endVisit(JsBinaryOperation x,
0108: JsContext<JsExpression> ctx) {
0109: if (isComma(x) == null) {
0110: return;
0111: }
0112:
0113: JsBinaryOperation toUpdate = isComma(x.getArg2());
0114: if (toUpdate == null) {
0115: return;
0116: }
0117:
0118: // Find the left-most, nested comma expression
0119: while (isComma(toUpdate.getArg1()) != null) {
0120: toUpdate = (JsBinaryOperation) toUpdate.getArg1();
0121: }
0122:
0123: /*
0124: * Create a new comma expression with the original LHS and the LHS of the
0125: * nested comma expression.
0126: */
0127: JsBinaryOperation newOp = new JsBinaryOperation(
0128: JsBinaryOperator.COMMA);
0129: newOp.setArg1(x.getArg1());
0130: newOp.setArg2(toUpdate.getArg1());
0131:
0132: // Set the LHS of the nested comma expression to the new comma expression
0133: toUpdate.setArg1(newOp);
0134:
0135: // Replace the original node with its updated RHS
0136: ctx.replaceMe(x.getArg2());
0137: }
0138: }
0139:
0140: /**
0141: * Provides a relative metric by which the syntactic complexity of a
0142: * JsExpression can be gauged.
0143: */
0144: private static class ComplexityEstimator extends JsVisitor {
0145: /**
0146: * The current measure of complexity. This measures the number of
0147: * expressions that have been encountered by the visitor.
0148: */
0149: private int complexity = 0;
0150:
0151: @Override
0152: public void endVisit(JsArrayAccess x,
0153: JsContext<JsExpression> ctx) {
0154: complexity++;
0155: }
0156:
0157: @Override
0158: public void endVisit(JsArrayLiteral x,
0159: JsContext<JsExpression> ctx) {
0160: complexity++;
0161: }
0162:
0163: @Override
0164: public void endVisit(JsBinaryOperation x,
0165: JsContext<JsExpression> ctx) {
0166: complexity++;
0167: }
0168:
0169: @Override
0170: public void endVisit(JsBooleanLiteral x,
0171: JsContext<JsExpression> ctx) {
0172: complexity++;
0173: }
0174:
0175: @Override
0176: public void endVisit(JsConditional x,
0177: JsContext<JsExpression> ctx) {
0178: complexity++;
0179: }
0180:
0181: @Override
0182: public void endVisit(JsDecimalLiteral x,
0183: JsContext<JsExpression> ctx) {
0184: complexity++;
0185: }
0186:
0187: @Override
0188: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
0189: complexity++;
0190: }
0191:
0192: @Override
0193: public void endVisit(JsIntegralLiteral x,
0194: JsContext<JsExpression> ctx) {
0195: complexity++;
0196: }
0197:
0198: @Override
0199: public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
0200: complexity++;
0201: }
0202:
0203: @Override
0204: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
0205: complexity++;
0206: }
0207:
0208: @Override
0209: public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
0210: complexity++;
0211: }
0212:
0213: @Override
0214: public void endVisit(JsNullLiteral x,
0215: JsContext<JsExpression> ctx) {
0216: complexity++;
0217: }
0218:
0219: @Override
0220: public void endVisit(JsObjectLiteral x,
0221: JsContext<JsExpression> ctx) {
0222: complexity++;
0223: }
0224:
0225: @Override
0226: public void endVisit(JsPostfixOperation x,
0227: JsContext<JsExpression> ctx) {
0228: complexity++;
0229: }
0230:
0231: @Override
0232: public void endVisit(JsPrefixOperation x,
0233: JsContext<JsExpression> ctx) {
0234: complexity++;
0235: }
0236:
0237: @Override
0238: public void endVisit(JsRegExp x, JsContext<JsExpression> ctx) {
0239: complexity++;
0240: }
0241:
0242: @Override
0243: public void endVisit(JsStringLiteral x,
0244: JsContext<JsExpression> ctx) {
0245: complexity++;
0246: }
0247:
0248: @Override
0249: public void endVisit(JsThisRef x, JsContext<JsExpression> ctx) {
0250: complexity++;
0251: }
0252:
0253: public int getComplexity() {
0254: return complexity;
0255: }
0256: }
0257:
0258: /**
0259: * This is used to clean up duplication invocations of functions that should
0260: * only be executed once, such as clinit functions. Whenever there is a
0261: * possible branch in program flow, the remover will create a new instance of
0262: * itself to handle the possible branches.
0263: *
0264: * We don't look at combining branch choices. This will not produce the most
0265: * efficient elimination of duplicated calls, but it handles the general case
0266: * and is simple to verify.
0267: */
0268: private static class DuplicateXORemover extends JsModVisitor {
0269: /*
0270: * TODO: Most of the special casing below can be removed if complex
0271: * statements always use blocks, rather than plain statements.
0272: */
0273:
0274: /**
0275: * Retains the the functions that we know have been called.
0276: */
0277: private final Set<JsFunction> called;
0278: private final JsProgram program;
0279:
0280: public DuplicateXORemover(JsProgram program) {
0281: this .program = program;
0282: called = new HashSet<JsFunction>();
0283: }
0284:
0285: public DuplicateXORemover(JsProgram program,
0286: Set<JsFunction> alreadyCalled) {
0287: this .program = program;
0288: called = new HashSet<JsFunction>(alreadyCalled);
0289: }
0290:
0291: /**
0292: * Look for comma expressions that contain duplicate calls and handle the
0293: * conditional-evaluation case of logical and/or operations.
0294: */
0295: @Override
0296: public boolean visit(JsBinaryOperation x,
0297: JsContext<JsExpression> ctx) {
0298: if (x.getOperator() == JsBinaryOperator.COMMA) {
0299:
0300: boolean left = isDuplicateCall(x.getArg1());
0301: boolean right = isDuplicateCall(x.getArg2());
0302:
0303: if (left && right) {
0304: /*
0305: * (clinit(), clinit()) --> delete or null.
0306: *
0307: * This construct is very unlikely since the InliningVisitor builds
0308: * the comma expressions in a right-nested manner.
0309: */
0310: if (ctx.canRemove()) {
0311: ctx.removeMe();
0312: return false;
0313: } else {
0314: // The return value from an XO function is never used
0315: ctx.replaceMe(program.getNullLiteral());
0316: return false;
0317: }
0318:
0319: } else if (left) {
0320: // (clinit(), xyz) --> xyz
0321: // This is the common case
0322: ctx.replaceMe(accept(x.getArg2()));
0323: return false;
0324:
0325: } else if (right) {
0326: // (xyz, clinit()) --> xyz
0327: // Possible if a clinit() were the last element
0328: ctx.replaceMe(accept(x.getArg1()));
0329: return false;
0330: }
0331:
0332: } else if (x.getOperator().equals(JsBinaryOperator.AND)
0333: || x.getOperator().equals(JsBinaryOperator.OR)) {
0334: x.setArg1(accept(x.getArg1()));
0335: // Possibility of conditional evaluation of second parameter
0336: x.setArg2(branch(x.getArg2()));
0337: return false;
0338: }
0339:
0340: return true;
0341: }
0342:
0343: /**
0344: * Most of the branching statements (as well as JsFunctions) will visit with
0345: * a JsBlock, so we don't need to explicitly enumerate all JsStatement
0346: * subtypes.
0347: */
0348: @Override
0349: public boolean visit(JsBlock x, JsContext<JsStatement> ctx) {
0350: branch(x.getStatements());
0351: return false;
0352: }
0353:
0354: @Override
0355: public boolean visit(JsCase x, JsContext<JsSwitchMember> ctx) {
0356: x.setCaseExpr(accept(x.getCaseExpr()));
0357: branch(x.getStmts());
0358: return false;
0359: }
0360:
0361: @Override
0362: public boolean visit(JsConditional x,
0363: JsContext<JsExpression> ctx) {
0364: x.setTestExpression(accept(x.getTestExpression()));
0365: x.setThenExpression(branch(x.getThenExpression()));
0366: x.setElseExpression(branch(x.getElseExpression()));
0367: return false;
0368: }
0369:
0370: @Override
0371: public boolean visit(JsDefault x, JsContext<JsSwitchMember> ctx) {
0372: branch(x.getStmts());
0373: return false;
0374: }
0375:
0376: @Override
0377: public boolean visit(JsExprStmt x, JsContext<JsStatement> ctx) {
0378: if (isDuplicateCall(x.getExpression())) {
0379: if (ctx.canRemove()) {
0380: ctx.removeMe();
0381: } else {
0382: ctx.replaceMe(program.getEmptyStmt());
0383: }
0384: return false;
0385:
0386: } else {
0387: return true;
0388: }
0389: }
0390:
0391: @Override
0392: public boolean visit(JsFor x, JsContext<JsStatement> ctx) {
0393: // The JsFor may have an expression xor a variable declaration.
0394: if (x.getInitExpr() != null) {
0395: x.setInitExpr(accept(x.getInitExpr()));
0396: } else if (x.getInitVars() != null) {
0397: x.setInitVars(accept(x.getInitVars()));
0398: }
0399:
0400: // The condition is optional
0401: if (x.getCondition() != null) {
0402: x.setCondition(accept(x.getCondition()));
0403: }
0404:
0405: // The increment expression is optional
0406: if (x.getIncrExpr() != null) {
0407: x.setIncrExpr(branch(x.getIncrExpr()));
0408: }
0409:
0410: // The body is not guaranteed to be a JsBlock
0411: x.setBody(branch(x.getBody()));
0412: return false;
0413: }
0414:
0415: @Override
0416: public boolean visit(JsForIn x, JsContext<JsStatement> ctx) {
0417: if (x.getIterExpr() != null) {
0418: x.setIterExpr(accept(x.getIterExpr()));
0419: }
0420:
0421: x.setObjExpr(accept(x.getObjExpr()));
0422:
0423: // The body is not guaranteed to be a JsBlock
0424: x.setBody(branch(x.getBody()));
0425: return false;
0426: }
0427:
0428: @Override
0429: public boolean visit(JsIf x, JsContext<JsStatement> ctx) {
0430: x.setIfExpr(accept(x.getIfExpr()));
0431:
0432: x.setThenStmt(branch(x.getThenStmt()));
0433: if (x.getElseStmt() != null) {
0434: x.setElseStmt(branch(x.getElseStmt()));
0435: }
0436:
0437: return false;
0438: }
0439:
0440: /**
0441: * Possibly record that we've seen a call in the current context.
0442: */
0443: @Override
0444: public boolean visit(JsInvocation x, JsContext<JsExpression> ctx) {
0445: JsFunction func = isExecuteOnce(x);
0446: while (func != null) {
0447: called.add(func);
0448: func = func.getImpliedExecute();
0449: }
0450: return true;
0451: }
0452:
0453: @Override
0454: public boolean visit(JsWhile x, JsContext<JsStatement> ctx) {
0455: x.setCondition(accept(x.getCondition()));
0456:
0457: // The body is not guaranteed to be a JsBlock
0458: x.setBody(branch(x.getBody()));
0459: return false;
0460: }
0461:
0462: private <T extends JsNode<T>> void branch(List<T> x) {
0463: DuplicateXORemover dup = new DuplicateXORemover(program,
0464: called);
0465: dup.acceptWithInsertRemove(x);
0466: didChange |= dup.didChange();
0467: }
0468:
0469: private <T extends JsNode<T>> T branch(T x) {
0470: DuplicateXORemover dup = new DuplicateXORemover(program,
0471: called);
0472: T toReturn = dup.accept(x);
0473:
0474: if ((toReturn != x) && !dup.didChange()) {
0475: throw new InternalCompilerException(
0476: "node replacement should imply didChange()");
0477: }
0478:
0479: didChange |= dup.didChange();
0480: return toReturn;
0481: }
0482:
0483: private boolean isDuplicateCall(JsExpression x) {
0484: if (!(x instanceof JsInvocation)) {
0485: return false;
0486: }
0487:
0488: JsFunction func = isExecuteOnce((JsInvocation) x);
0489: return (func != null && called.contains(func));
0490: }
0491: }
0492:
0493: /**
0494: * Determines if a list of names is guaranteed to be evaluated in a particular
0495: * order.
0496: */
0497: private static class EvaluationOrderVisitor extends JsVisitor {
0498: private boolean maintainsOrder = true;
0499: private final List<JsName> toEvaluate;
0500: private final List<JsName> unevaluated;
0501:
0502: public EvaluationOrderVisitor(List<JsName> toEvaluate) {
0503: this .toEvaluate = toEvaluate;
0504: this .unevaluated = new ArrayList<JsName>(toEvaluate);
0505: }
0506:
0507: @Override
0508: public void endVisit(JsBinaryOperation x,
0509: JsContext<JsExpression> ctx) {
0510: JsBinaryOperator op = x.getOperator();
0511:
0512: /*
0513: * We don't care about the left-hand expression, because it is guaranteed
0514: * to be evaluated.
0515: */
0516: boolean rightStrict = refersToRequiredName(x.getArg2());
0517: boolean conditionalEvaluation = JsBinaryOperator.AND
0518: .equals(op)
0519: || JsBinaryOperator.OR.equals(op);
0520:
0521: if (rightStrict && conditionalEvaluation) {
0522: maintainsOrder = false;
0523: }
0524: }
0525:
0526: /**
0527: * If the condition would cause conditional evaluation of strict parameters,
0528: * don't allow inlining.
0529: */
0530: @Override
0531: public void endVisit(JsConditional x,
0532: JsContext<JsExpression> ctx) {
0533: boolean thenStrict = refersToRequiredName(x
0534: .getThenExpression());
0535: boolean elseStrict = refersToRequiredName(x
0536: .getElseExpression());
0537:
0538: if (thenStrict || elseStrict) {
0539: maintainsOrder = false;
0540: }
0541: }
0542:
0543: /**
0544: * The statement declares a function closure. This makes actual evaluation
0545: * order of the parameters difficult or impossible to determine, so we'll
0546: * just ignore them.
0547: */
0548: @Override
0549: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
0550: maintainsOrder = false;
0551: }
0552:
0553: /**
0554: * The innermost invocation we see must consume all presently unevaluated
0555: * parameters to ensure that an exception does not prevent their evaluation.
0556: *
0557: * In the case of a nested invocation, such as
0558: * <code>F(r1, r2, G(r3, r4), f1);</code> the evaluation order is
0559: * guaranteed to be maintained, provided that no required parameters occur
0560: * after the nested invocation.
0561: */
0562: @Override
0563: public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
0564: /*
0565: * The check for isExecuteOnce() is potentially incorrect here, however
0566: * the original Java semantics of the clinit would have made the code
0567: * incorrect anyway.
0568: */
0569: if ((isExecuteOnce(x) == null) && unevaluated.size() > 0) {
0570: maintainsOrder = false;
0571: }
0572: }
0573:
0574: @Override
0575: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
0576: JsName name = x.getName();
0577:
0578: if (!toEvaluate.contains(name)) {
0579: return;
0580: }
0581:
0582: if (unevaluated.size() == 0
0583: || !unevaluated.remove(0).equals(name)) {
0584: maintainsOrder = false;
0585: }
0586: }
0587:
0588: public boolean maintainsOrder() {
0589: return maintainsOrder && unevaluated.size() == 0;
0590: }
0591:
0592: /**
0593: * Determine if an expression contains a reference to a strict parameter.
0594: */
0595: private boolean refersToRequiredName(JsExpression e) {
0596: RefersToNameVisitor v = new RefersToNameVisitor(toEvaluate);
0597: v.accept(e);
0598: return v.refersToName();
0599: }
0600: }
0601:
0602: /**
0603: * Collect all of the idents used in an AST node. The collector can be
0604: * configured to collect idents from qualified xor unqualified JsNameRefs.
0605: */
0606: private static class IdentCollector extends JsVisitor {
0607: private final boolean collectQualified;
0608: private final Set<String> idents = new HashSet<String>();
0609:
0610: public IdentCollector(boolean collectQualified) {
0611: this .collectQualified = collectQualified;
0612: }
0613:
0614: @Override
0615: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
0616: boolean hasQualifier = x.getQualifier() != null;
0617:
0618: if ((collectQualified && !hasQualifier)
0619: || (!collectQualified && hasQualifier)) {
0620: return;
0621: }
0622:
0623: assert x.getIdent() != null;
0624: idents.add(x.getIdent());
0625: }
0626:
0627: public Set<String> getIdents() {
0628: return idents;
0629: }
0630: }
0631:
0632: /**
0633: * This class looks for function invocations that can be inlined and performs
0634: * the replacement by replacing the JsInvocation with a comma expression
0635: * consisting of the expressions evaluated by the target function. A second
0636: * step may convert the expressions in the comma expression back to multiple
0637: * statements if the context of the invocation would allow this.
0638: */
0639: private static class InliningVisitor extends JsModVisitor {
0640: private final Set<JsFunction> blacklist = new HashSet<JsFunction>();
0641: private final Stack<JsFunction> functionStack = new Stack<JsFunction>();
0642: private final JsProgram program;
0643:
0644: public InliningVisitor(JsProgram program) {
0645: this .program = program;
0646: }
0647:
0648: /**
0649: * Add to the list of JsFunctions that should not be inlined, regardless of
0650: * whether or not they would normally be inlinable.
0651: */
0652: public void blacklist(Collection<JsFunction> functions) {
0653: blacklist.addAll(functions);
0654: }
0655:
0656: /**
0657: * This normalizes the comma expressions into multiple statements and
0658: * removes statements with no side-effects.
0659: */
0660: @Override
0661: public void endVisit(JsExprStmt x, JsContext<JsStatement> ctx) {
0662: JsExpression e = x.getExpression();
0663:
0664: // We will occasionally create JsExprStmts that have no side-effects.
0665: if (ctx.canRemove() && !hasSideEffects(x)) {
0666: ctx.removeMe();
0667: return;
0668: }
0669:
0670: List<JsExprStmt> statements = new ArrayList<JsExprStmt>();
0671:
0672: /*
0673: * Assemble the expressions back into a list of JsExprStmts. We will
0674: * iteratively disassemble the nested comma expressions, stopping when the
0675: * LHS is not a comma expression.
0676: */
0677: while (e instanceof JsBinaryOperation) {
0678: JsBinaryOperation op = (JsBinaryOperation) e;
0679:
0680: if (!op.getOperator().equals(JsBinaryOperator.COMMA)) {
0681: break;
0682: }
0683:
0684: /*
0685: * We can ignore intermediate expressions as long as they have no
0686: * side-effects.
0687: */
0688: if (hasSideEffects(op.getArg2())) {
0689: statements.add(0, new JsExprStmt(op.getArg2()));
0690: }
0691:
0692: e = op.getArg1();
0693: }
0694:
0695: /*
0696: * We know the return value from the original invocation was ignored, so
0697: * it may be possible to ignore the final expressions as long as it has no
0698: * side-effects.
0699: */
0700: if (hasSideEffects(e)) {
0701: statements.add(0, new JsExprStmt(e));
0702: }
0703:
0704: if (statements.size() == 0) {
0705: // The expression contained no side effects at all.
0706: if (ctx.canRemove()) {
0707: ctx.removeMe();
0708: } else {
0709: ctx.replaceMe(program.getEmptyStmt());
0710: }
0711:
0712: } else if (x.getExpression() != statements.get(0)
0713: .getExpression()) {
0714: // Something has changed
0715:
0716: if (!ctx.canInsert()) {
0717: /*
0718: * This indicates that the function was attached to a clause of a
0719: * control function and not into an existing block. We'll replace the
0720: * single JsExprStmt with a JsBlock that contains all of the
0721: * statements.
0722: */
0723: JsBlock b = new JsBlock();
0724: b.getStatements().addAll(statements);
0725: ctx.replaceMe(b);
0726: return;
0727:
0728: } else {
0729: // Insert the new statements into the original context
0730: for (JsStatement s : statements) {
0731: ctx.insertBefore(s);
0732: }
0733: ctx.removeMe();
0734: }
0735: }
0736: }
0737:
0738: @Override
0739: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
0740: if (!functionStack.pop().equals(x)) {
0741: throw new InternalCompilerException(
0742: "Unexpected function popped");
0743: }
0744: }
0745:
0746: @Override
0747: public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
0748: /*
0749: * We only want to look at invocations of things that we statically know
0750: * to be functions. Otherwise, we can't know what statements the
0751: * invocation would actually invoke. The static reference would be null
0752: * when trying operate on references to external functions, or functions
0753: * as arguments to another function.
0754: */
0755: JsFunction f = isFunction(x.getQualifier());
0756: if (f == null) {
0757: return;
0758: }
0759:
0760: // Don't inline blacklisted functions
0761: if (blacklist.contains(f)) {
0762: return;
0763: }
0764:
0765: List<JsStatement> statements = f.getBody().getStatements();
0766: List<JsExpression> hoisted = new ArrayList<JsExpression>(
0767: statements.size());
0768:
0769: boolean sawReturnStatement = false;
0770: for (JsStatement statement : statements) {
0771: /*
0772: * Create replacement expressions to use in place of the original
0773: * statements. It is important that the replacement is newly-minted and
0774: * therefore not referenced by any other AST nodes. Consider the case of
0775: * a common, delegating function. If the hoisted expressions were not
0776: * distinct objects, it would not be possible to substitute different
0777: * JsNameRefs at different call sites.
0778: */
0779: JsExpression h = hoistedExpression(statement);
0780: if (h == null) {
0781: return;
0782: }
0783:
0784: hoisted.add(h);
0785:
0786: if (isReturnStatement(statement)) {
0787: sawReturnStatement = true;
0788: break;
0789: }
0790: }
0791:
0792: /*
0793: * If the inlined method has no return statement, synthesize an undefined
0794: * reference. It will be reclaimed if the method call is from a
0795: * JsExprStmt.
0796: */
0797: if (!sawReturnStatement) {
0798: hoisted.add(program.getUndefinedLiteral());
0799: }
0800:
0801: assert (hoisted.size() > 0);
0802:
0803: /*
0804: * Build up the new comma expression from right-to-left; building the
0805: * rightmost comma expressions first. Bootstrapping with i.previous()
0806: * ensures that this logic will function correctly in the case of a single
0807: * expression.
0808: */
0809: ListIterator<JsExpression> i = hoisted.listIterator(hoisted
0810: .size());
0811: JsExpression op = i.previous();
0812: while (i.hasPrevious()) {
0813: JsBinaryOperation outerOp = new JsBinaryOperation(
0814: JsBinaryOperator.COMMA);
0815: outerOp.setArg1(i.previous());
0816: outerOp.setArg2(op);
0817: op = outerOp;
0818: }
0819:
0820: // Confirm that the expression conforms to the desired heuristics
0821: if (!isInlinable(functionStack.peek(), x, f, op)) {
0822: return;
0823: }
0824:
0825: // Perform the name replacement
0826: NameRefReplacerVisitor v = new NameRefReplacerVisitor(x, f);
0827: op = v.accept(op);
0828:
0829: // Normalize any nested comma expressions that we may have generated.
0830: op = (new CommaNormalizer()).accept(op);
0831:
0832: /*
0833: * Compare the relative complexity of the original invocation versus the
0834: * inlined form.
0835: */
0836: int originalComplexity = complexity(x);
0837: int inlinedComplexity = complexity(op);
0838: if (((float) inlinedComplexity / originalComplexity) > MAX_COMPLEXITY_INCREASE) {
0839: return;
0840: }
0841:
0842: /*
0843: * See if any further inlining can be performed in the current context. By
0844: * attempting to maximize the level of inlining now, we can reduce the
0845: * total number of passes required to finalize the AST.
0846: */
0847: op = accept(op);
0848:
0849: ctx.replaceMe(op);
0850: }
0851:
0852: @Override
0853: public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
0854: functionStack.push(x);
0855: return true;
0856: }
0857: }
0858:
0859: /**
0860: * Replace references to JsNames with the inlined JsExpression.
0861: */
0862: private static class NameRefReplacerVisitor extends JsModVisitor {
0863: /**
0864: * Set up a map of parameter names back to the expressions that will be
0865: * passed in from the outer call site.
0866: */
0867: final Map<JsName, JsExpression> paramsToArgsMap = new HashMap<JsName, JsExpression>();
0868:
0869: /**
0870: * Constructor.
0871: *
0872: * @param invocation The call site
0873: * @param function The function that encloses the inlined statement
0874: */
0875: public NameRefReplacerVisitor(JsInvocation invocation,
0876: JsFunction function) {
0877: List<JsParameter> parameters = function.getParameters();
0878: List<JsExpression> arguments = invocation.getArguments();
0879:
0880: if (parameters.size() != arguments.size()) {
0881: // This shouldn't happen if the cloned JsInvocation has been properly
0882: // configured
0883: throw new InternalCompilerException(
0884: "Mismatch on parameters and arguments");
0885: }
0886:
0887: for (int i = 0; i < parameters.size(); i++) {
0888: JsParameter p = parameters.get(i);
0889: JsExpression e = arguments.get(i);
0890: paramsToArgsMap.put(p.getName(), e);
0891: }
0892: }
0893:
0894: /**
0895: * Replace JsNameRefs that refer to parameters with the expression passed
0896: * into the function invocation.
0897: */
0898: @Override
0899: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
0900: if (x.getQualifier() != null) {
0901: return;
0902: }
0903:
0904: /*
0905: * TODO if we ever allow mutable JsExpression types to be considered
0906: * always flexible, then it would be necessary to clone the expression.
0907: */
0908: JsExpression original = paramsToArgsMap.get(x.getName());
0909:
0910: if (original != null) {
0911: ctx.replaceMe(original);
0912: }
0913: }
0914: }
0915:
0916: /**
0917: * Detects uses of parameters that would produce incorrect results if inlined.
0918: * Generally speaking, we disallow the use of parameters as lvalues.
0919: */
0920: private static class ParameterUsageVisitor extends JsVisitor {
0921: private boolean lvalue = false;
0922: private final Set<JsName> parameterNames;
0923:
0924: public ParameterUsageVisitor(Set<JsName> parameterNames) {
0925: this .parameterNames = parameterNames;
0926: }
0927:
0928: /**
0929: * Disallow inlining if the left-hand side of an assignment is a parameter.
0930: */
0931: @Override
0932: public void endVisit(JsBinaryOperation x,
0933: JsContext<JsExpression> ctx) {
0934: JsBinaryOperator op = x.getOperator();
0935:
0936: // Don't allow assignments to the left-hand side.
0937: if (op.isAssignment() && isParameter(x.getArg1())) {
0938: lvalue = true;
0939: }
0940: }
0941:
0942: /**
0943: * Delegates to {@link #checkUnaryOperation(JsUnaryOperation)}.
0944: */
0945: @Override
0946: public void endVisit(JsPostfixOperation x,
0947: JsContext<JsExpression> ctx) {
0948: checkUnaryOperation(x);
0949: }
0950:
0951: /**
0952: * Delegates to {@link #checkUnaryOperation(JsUnaryOperation)}.
0953: */
0954: @Override
0955: public void endVisit(JsPrefixOperation x,
0956: JsContext<JsExpression> ctx) {
0957: checkUnaryOperation(x);
0958: }
0959:
0960: public boolean parameterAsLValue() {
0961: return lvalue;
0962: }
0963:
0964: /**
0965: * Disallow modification of parameters via unary operations.
0966: */
0967: private void checkUnaryOperation(JsUnaryOperation x) {
0968: if (x.getOperator().isModifying()
0969: && isParameter(x.getArg())) {
0970: lvalue = true;
0971: }
0972: }
0973:
0974: /**
0975: * Determine if a JsExpression is a JsNameRef that refers to a parameter.
0976: */
0977: private boolean isParameter(JsExpression e) {
0978: if (!(e instanceof JsNameRef)) {
0979: return false;
0980: }
0981:
0982: JsNameRef ref = (JsNameRef) e;
0983: if (ref.getQualifier() != null) {
0984: return false;
0985: }
0986:
0987: JsName name = ref.getName();
0988: return parameterNames.contains(name);
0989: }
0990: }
0991:
0992: /**
0993: * Collect self-recursive functions. This visitor does not look for
0994: * mutually-recursive functions because inlining one of the functions into the
0995: * other would make the single resultant function self-recursive and not
0996: * eligible for inlining in a subsequent pass.
0997: */
0998: private static class RecursionCollector extends JsVisitor {
0999: private final Stack<JsFunction> functionStack = new Stack<JsFunction>();
1000: private final Set<JsFunction> recursive = new HashSet<JsFunction>();
1001:
1002: @Override
1003: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
1004: if (!functionStack.pop().equals(x)) {
1005: throw new InternalCompilerException(
1006: "Unexpected function popped");
1007: }
1008: }
1009:
1010: @Override
1011: public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
1012: /*
1013: * Because functions can encapsulate other functions, we look at the
1014: * entire stack and not just the top element. This would prevent inlining
1015: *
1016: * function a() { function b() { a(); } b(); }
1017: *
1018: * in the case that we generally allow nested functions to be inlinable.
1019: */
1020: JsFunction f = isFunction(x.getQualifier());
1021: if (functionStack.contains(f)) {
1022: recursive.add(f);
1023: }
1024: }
1025:
1026: public Set<JsFunction> getRecursive() {
1027: return recursive;
1028: }
1029:
1030: @Override
1031: public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
1032: functionStack.push(x);
1033: return true;
1034: }
1035: }
1036:
1037: /**
1038: * Determine which functions should not be inlined because they are redefined
1039: * during program execution. This would violate the assumption that the
1040: * statements to be executed by any given function invocation are stable over
1041: * the lifetime of the program.
1042: */
1043: private static class RedefinedFunctionCollector extends JsVisitor {
1044: private final Map<JsName, JsFunction> nameMap = new IdentityHashMap<JsName, JsFunction>();
1045: private final Set<JsFunction> redefined = new HashSet<JsFunction>();
1046:
1047: /**
1048: * Look for assignments to JsNames whose static references are JsFunctions.
1049: */
1050: @Override
1051: public void endVisit(JsBinaryOperation x,
1052: JsContext<JsExpression> ctx) {
1053:
1054: if (!x.getOperator().equals(JsBinaryOperator.ASG)) {
1055: return;
1056: }
1057:
1058: JsFunction f = isFunction(x.getArg1());
1059: if (f != null) {
1060: redefined.add(f);
1061: }
1062: }
1063:
1064: /**
1065: * Look for the case where a function is declared with the same name as an
1066: * existing function.
1067: */
1068: @Override
1069: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
1070: JsName name = x.getName();
1071:
1072: if (name == null) {
1073: // Ignore anonymous functions
1074: return;
1075:
1076: } else if (nameMap.containsKey(name)) {
1077: /*
1078: * We have to add the current function as well as the original
1079: * JsFunction that was declared to use that name.
1080: */
1081: redefined.add(nameMap.get(name));
1082: redefined.add(x);
1083: } else {
1084: nameMap.put(name, x);
1085: }
1086: }
1087:
1088: public Collection<JsFunction> getRedefined() {
1089: return redefined;
1090: }
1091: }
1092:
1093: /**
1094: * Given a collection of JsNames, determine if an AST node refers to any of
1095: * those names.
1096: */
1097: private static class RefersToNameVisitor extends JsVisitor {
1098: private final Collection<JsName> names;
1099: private boolean refersToName;
1100: private boolean refersToUnbound;
1101:
1102: public RefersToNameVisitor(Collection<JsName> names) {
1103: this .names = names;
1104: }
1105:
1106: @Override
1107: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
1108: JsName name = x.getName();
1109:
1110: if (name == null) {
1111: refersToUnbound = true;
1112: } else {
1113: refersToName = refersToName || names.contains(name);
1114: }
1115: }
1116:
1117: public boolean refersToName() {
1118: return refersToName;
1119: }
1120:
1121: public boolean refersToUnbound() {
1122: return refersToUnbound;
1123: }
1124: }
1125:
1126: /**
1127: * Examine a node to determine if it might produce side effects.
1128: */
1129: private static class SideEffectsVisitor extends JsVisitor {
1130: private boolean hasSideEffects;
1131:
1132: @Override
1133: public void endVisit(JsBinaryOperation x,
1134: JsContext<JsExpression> ctx) {
1135: hasSideEffects |= (x.getOperator().isAssignment());
1136: }
1137:
1138: @Override
1139: public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
1140: /*
1141: * Declaring a named function implicitly has an assignment side-effect.
1142: */
1143: hasSideEffects |= x.getName() != null;
1144: }
1145:
1146: @Override
1147: public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
1148: /*
1149: * We don't actually need to drill-down into other functions to see if
1150: * they do or do not have side-effects. The simple, side-effect free
1151: * function invocations will naturally be inlined in subsequent
1152: * iterations.
1153: */
1154: hasSideEffects = true;
1155: }
1156:
1157: @Override
1158: public void endVisit(JsNew x, JsContext<JsExpression> ctx) {
1159: /*
1160: * The typical use of the new keyword in JavaScript generated by GWT is to
1161: * create a prototypical object, and then pass it into a Java-derived
1162: * constructor. Given that the majority of the uses of new would not
1163: * benefit from inlining, it's not worth the extra complexity of worrying
1164: * about yet another set of special cases.
1165: */
1166: hasSideEffects = true;
1167: }
1168:
1169: @Override
1170: public void endVisit(JsPostfixOperation x,
1171: JsContext<JsExpression> ctx) {
1172: hasSideEffects |= x.getOperator().isModifying();
1173: }
1174:
1175: @Override
1176: public void endVisit(JsPrefixOperation x,
1177: JsContext<JsExpression> ctx) {
1178: hasSideEffects |= x.getOperator().isModifying();
1179: }
1180:
1181: @Override
1182: public void endVisit(JsThrow x, JsContext<JsStatement> ctx) {
1183: hasSideEffects = true;
1184: }
1185:
1186: public boolean hasSideEffects() {
1187: return hasSideEffects;
1188: }
1189: }
1190:
1191: /**
1192: * This ensures that changing the scope of an expression from its enclosing
1193: * function into the scope of the call site will not cause unqualified
1194: * identifiers to resolve to different values.
1195: */
1196: private static class StableNameChecker extends JsVisitor {
1197: private final JsScope calleeScope;
1198: private final JsScope callerScope;
1199: private final Collection<JsName> parameterNames;
1200: private boolean stable = true;
1201:
1202: public StableNameChecker(JsScope callerScope,
1203: JsScope calleeScope, Collection<JsName> parameterNames) {
1204: this .callerScope = callerScope;
1205: this .calleeScope = calleeScope;
1206: this .parameterNames = parameterNames;
1207: }
1208:
1209: @Override
1210: public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
1211: /*
1212: * We can ignore qualified reference, since their scope is always that of
1213: * the qualifier.
1214: */
1215: if (x.getQualifier() != null) {
1216: return;
1217: }
1218:
1219: /*
1220: * Attempt to resolve the ident in both scopes
1221: */
1222: JsName callerName = callerScope.findExistingName(x
1223: .getIdent());
1224: JsName calleeName = calleeScope.findExistingName(x
1225: .getIdent());
1226:
1227: if (callerName == null && calleeName == null) {
1228: // They both reference out-of-module names
1229:
1230: } else if (parameterNames.contains(calleeName)) {
1231: // A reference to a parameter, which will be replaced by an argument
1232:
1233: } else if (callerName != null
1234: && callerName.equals(calleeName)) {
1235: // The names are known to us and are the same
1236:
1237: } else {
1238: stable = false;
1239: }
1240: }
1241:
1242: public boolean isStable() {
1243: return stable;
1244: }
1245: }
1246:
1247: /**
1248: * A List of expression types that are known to never be affected by
1249: * side-effects. Used by {@link #alwaysFlexible(JsExpression)}.
1250: */
1251: private static final List<Class<?>> ALWAYS_FLEXIBLE = Arrays
1252: .asList(new Class<?>[] { JsBooleanLiteral.class,
1253: JsDecimalLiteral.class, JsIntegralLiteral.class,
1254: JsNullLiteral.class, JsRegExp.class,
1255: JsStringLiteral.class, JsThisRef.class });
1256:
1257: /**
1258: * When attempting to inline an invocation, this constant determines the
1259: * maximum allowable ratio of potential inlined complexity to initial
1260: * complexity. This acts as a brake on very large expansions from bloating the
1261: * the generated output. Increasing this number will allow larger sections of
1262: * code to be inlined, but at a cost of larger JS output.
1263: */
1264: private static final int MAX_COMPLEXITY_INCREASE = 5;
1265:
1266: /**
1267: * Static entry point used by JavaToJavaScriptCompiler.
1268: */
1269: public static boolean exec(JsProgram program) {
1270: RedefinedFunctionCollector d = new RedefinedFunctionCollector();
1271: d.accept(program);
1272:
1273: RecursionCollector rc = new RecursionCollector();
1274: rc.accept(program);
1275:
1276: InliningVisitor v = new InliningVisitor(program);
1277: v.blacklist(d.getRedefined());
1278: v.blacklist(rc.getRecursive());
1279: v.accept(program);
1280:
1281: DuplicateXORemover r = new DuplicateXORemover(program);
1282: r.accept(program);
1283:
1284: return v.didChange() || r.didChange();
1285: }
1286:
1287: /**
1288: * Indicates if an expression can be repeated multiple times in a delegation
1289: * removal without side-effects.
1290: */
1291: private static boolean alwaysFlexible(JsExpression e) {
1292: if (e instanceof JsNameRef) {
1293: return false;
1294: } else {
1295: return ALWAYS_FLEXIBLE.contains(e.getClass());
1296: }
1297: }
1298:
1299: private static int complexity(JsNode<?> toEstimate) {
1300: ComplexityEstimator e = new ComplexityEstimator();
1301: e.accept(toEstimate);
1302: return e.getComplexity();
1303: }
1304:
1305: /**
1306: * Check to see if the to-be-inlined statement shares any idents with the
1307: * call-side arguments. Two passes are made: the first one looks for qualified
1308: * names; the second pass looks for unqualified names, but ignores identifiers
1309: * that refer to function parameters.
1310: */
1311: private static boolean hasCommonIdents(
1312: List<JsExpression> arguments, JsNode<?> toInline,
1313: Collection<String> parameterIdents) {
1314:
1315: // This is a fire-twice loop
1316: boolean checkQualified = false;
1317: do {
1318: checkQualified = !checkQualified;
1319:
1320: // Collect the idents used in the arguments and the statement
1321: IdentCollector argCollector = new IdentCollector(
1322: checkQualified);
1323: argCollector.acceptList(arguments);
1324: IdentCollector statementCollector = new IdentCollector(
1325: checkQualified);
1326: statementCollector.accept(toInline);
1327:
1328: Set<String> idents = argCollector.getIdents();
1329:
1330: // Unqualified idents may be references to parameters, thus ignored
1331: if (!checkQualified) {
1332: idents.removeAll(parameterIdents);
1333: }
1334:
1335: // Perform the set difference
1336: idents.retainAll(statementCollector.getIdents());
1337:
1338: if (idents.size() > 0) {
1339: return true;
1340: }
1341: } while (checkQualified);
1342:
1343: return false;
1344: }
1345:
1346: /**
1347: * Determine whether or not an AST node has side effects.
1348: */
1349: private static boolean hasSideEffects(JsVisitable<?> e) {
1350: SideEffectsVisitor v = new SideEffectsVisitor();
1351: v.accept(e);
1352: return v.hasSideEffects();
1353: }
1354:
1355: /**
1356: * Determine whether or not a list of AST nodes have side effects.
1357: */
1358: private static <T extends JsVisitable<T>> boolean hasSideEffects(
1359: List<T> list) {
1360: SideEffectsVisitor v = new SideEffectsVisitor();
1361: v.acceptList(list);
1362: return v.hasSideEffects();
1363: }
1364:
1365: /**
1366: * Given a delegated JsStatement, construct an expression to hoist into the
1367: * outer caller. This does not perform any name replacement, but simply
1368: * constructs a mutable copy of the expression that can be manipulated
1369: * at-will.
1370: */
1371: private static JsExpression hoistedExpression(JsStatement statement) {
1372: JsExpression expression;
1373: if (statement instanceof JsExprStmt) {
1374: JsExprStmt exprStmt = (JsExprStmt) statement;
1375: expression = exprStmt.getExpression();
1376: } else if (statement instanceof JsReturn) {
1377: JsReturn ret = (JsReturn) statement;
1378: expression = ret.getExpr();
1379: } else {
1380: return null;
1381: }
1382:
1383: return JsHoister.hoist(expression);
1384: }
1385:
1386: /**
1387: * Given a JsInvocation, determine if it is invoking a JsFunction that is
1388: * specified to be executed only once during the program's lifetime.
1389: */
1390: private static JsFunction isExecuteOnce(JsInvocation invocation) {
1391: JsFunction f = isFunction(invocation.getQualifier());
1392: if (f != null && f.getExecuteOnce()) {
1393: return f;
1394: }
1395: return null;
1396: }
1397:
1398: /**
1399: * Given an expression, determine if it it is a JsNameRef that refers to a
1400: * statically-defined JsFunction.
1401: */
1402: private static JsFunction isFunction(JsExpression e) {
1403: if (e instanceof JsNameRef) {
1404: JsNameRef ref = (JsNameRef) e;
1405: JsNode staticRef = ref.getName().getStaticRef();
1406: if (staticRef instanceof JsFunction) {
1407: return (JsFunction) staticRef;
1408: }
1409: }
1410:
1411: return null;
1412: }
1413:
1414: /**
1415: * Determine if a statement can be inlined into a call site.
1416: */
1417: private static boolean isInlinable(JsFunction caller,
1418: JsInvocation invocation, JsFunction callee,
1419: JsNode<?> toInline) {
1420: List<JsExpression> arguments = invocation.getArguments();
1421:
1422: /*
1423: * This will happen with varargs-style JavaScript functions that rely on the
1424: * "arguments" array. The reference to arguments would be detected in
1425: * BoundedScopeVisitor, but the code below assumes the same number of
1426: * parameters and arguments.
1427: */
1428: if (arguments.size() != callee.getParameters().size()) {
1429: return false;
1430: }
1431:
1432: // Build up a list of all parameter names
1433: Set<JsName> parameterNames = new HashSet<JsName>();
1434: Set<String> parameterIdents = new HashSet<String>();
1435: for (JsParameter param : callee.getParameters()) {
1436: parameterNames.add(param.getName());
1437: parameterIdents.add(param.getName().getIdent());
1438: }
1439:
1440: /*
1441: * Make sure that inlining won't change the final name of non-parameter
1442: * idents due to the change of scope. The most likely cause would be the use
1443: * of an unqualified variable reference in a JSNI block that happened to
1444: * conflict with a Java-derived identifier.
1445: */
1446: StableNameChecker detector = new StableNameChecker(caller
1447: .getScope(), callee.getScope(), parameterNames);
1448: detector.accept(toInline);
1449: if (!detector.isStable()) {
1450: return false;
1451: }
1452:
1453: /*
1454: * Ensure that the names referred to by the argument list and the statement
1455: * are disjoint. This prevents inlining of the following:
1456: *
1457: * static int i; public void add(int a) { i += a; }; add(i++);
1458: *
1459: */
1460: if (hasCommonIdents(arguments, toInline, parameterIdents)) {
1461: return false;
1462: }
1463:
1464: /*
1465: * Determine if the evaluation of the invocation's arguments may create side
1466: * effects. This will determine how aggressively the parameters may be
1467: * reordered.
1468: */
1469: if (hasSideEffects(arguments)) {
1470: /*
1471: * Determine the order in which the parameters must be evaluated. This
1472: * will vary between call sites, based on whether or not the invocation's
1473: * arguments can be repeated without ill effect.
1474: */
1475: List<JsName> requiredOrder = new ArrayList<JsName>();
1476: for (int i = 0; i < arguments.size(); i++) {
1477: JsExpression e = arguments.get(i);
1478: JsParameter p = callee.getParameters().get(i);
1479:
1480: if (!alwaysFlexible(e)) {
1481: requiredOrder.add(p.getName());
1482: }
1483: }
1484:
1485: /*
1486: * Verify that the non-reorderable arguments are evaluated in the right
1487: * order.
1488: */
1489: if (requiredOrder.size() > 0) {
1490: EvaluationOrderVisitor orderVisitor = new EvaluationOrderVisitor(
1491: requiredOrder);
1492: orderVisitor.accept(toInline);
1493: if (!orderVisitor.maintainsOrder()) {
1494: return false;
1495: }
1496: }
1497: }
1498:
1499: // Check that parameters aren't used in such a way as to prohibit inlining
1500: ParameterUsageVisitor v = new ParameterUsageVisitor(
1501: parameterNames);
1502: v.accept(toInline);
1503: if (v.parameterAsLValue()) {
1504: return false;
1505: }
1506:
1507: // Hooray!
1508: return true;
1509: }
1510:
1511: /**
1512: * This is used in combination with {@link #hoistedExpression(JsStatement)} to
1513: * indicate if a given statement would terminate the list of hoisted
1514: * expressions.
1515: */
1516: private static boolean isReturnStatement(JsStatement statement) {
1517: return statement instanceof JsReturn;
1518: }
1519:
1520: /**
1521: * Utility class.
1522: */
1523: private JsInliner() {
1524: }
1525: }
|