Refactor JsInliner to support FORCE_INLINE.
Change-Id: I596daf7eaf04046648834f95cb2c706478953d8d
diff --git a/dev/BUILD b/dev/BUILD
index 996a4e1..bc36f70 100644
--- a/dev/BUILD
+++ b/dev/BUILD
@@ -185,6 +185,7 @@
"core/src/com/google/gwt/dev/PrecompilationResult.java",
"core/src/com/google/gwt/dev/StringAnalyzableTypeEnvironment.java",
"core/src/com/google/gwt/dev/cfg/**/*.java",
+ "core/src/com/google/gwt/dev/common/**/*.java",
"core/src/com/google/gwt/dev/javac/**/*.java",
"core/src/com/google/gwt/dev/jdt/**/*.java",
"core/src/com/google/gwt/dev/jjs/**/*.java",
diff --git a/dev/core/src/com/google/gwt/dev/common/InliningMode.java b/dev/core/src/com/google/gwt/dev/common/InliningMode.java
new file mode 100644
index 0000000..2e2f2f2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/common/InliningMode.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.common;
+
+/**
+ * Used to mark methods to be handled in specific ways by inliners.
+ */
+public enum InliningMode {
+ NORMAL, DO_NOT_INLINE, FORCE_INLINE;
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 5bef255..7798bc2 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -133,6 +133,7 @@
import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
import com.google.gwt.dev.js.JsDuplicateCaseFolder;
import com.google.gwt.dev.js.JsDuplicateFunctionRemover;
+import com.google.gwt.dev.js.JsForceInliningChecker;
import com.google.gwt.dev.js.JsIncrementalNamer;
import com.google.gwt.dev.js.JsInliner;
import com.google.gwt.dev.js.JsLiteralInterner;
@@ -392,6 +393,9 @@
// (7) Optimize the JS AST.
final Set<JsNode> inlinableJsFunctions = jjsMapAndInlineableFunctions.getRight();
optimizeJs(inlinableJsFunctions);
+ if (options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
+ JsForceInliningChecker.check(logger, jjsmap, jsProgram);
+ }
// TODO(stalcup): move to normalization
// Must run before code splitter and namer.
@@ -497,7 +501,8 @@
}
}
- private void optimizeJs(Set<JsNode> inlinableJsFunctions) throws InterruptedException {
+ private void optimizeJs(Set<JsNode> inlinableJsFunctions)
+ throws InterruptedException, UnableToCompleteException {
if (shouldOptimize()) {
optimizeJsLoop(inlinableJsFunctions);
JsDuplicateCaseFolder.exec(jsProgram);
@@ -994,7 +999,8 @@
return new BufferedInputStream(new GZIPInputStream(artifact.getContents(TreeLogger.NULL)));
}
- private void optimizeJsLoop(Collection<JsNode> toInline) throws InterruptedException {
+ private void optimizeJsLoop(Collection<JsNode> toInline)
+ throws InterruptedException, UnableToCompleteException {
int optimizationLevel = options.getOptimizationLevel();
List<OptimizerStats> allOptimizerStats = Lists.newArrayList();
int counter = 0;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index 538684e..1071a3c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.jjs.ast;
+import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
@@ -45,7 +46,7 @@
* patterns, then it will marked as {@code UNDEFINED} to be later signaled as error in
* {@link com.google.gwt.dev.jjs.impl.JsInteropRestrictionChecker}.
*/
- public static enum JsPropertyAccessorType {
+ public enum JsPropertyAccessorType {
GETTER, SETTER, UNDEFINED;
}
@@ -61,7 +62,8 @@
private String exportNamespace;
private JsPropertyAccessorType jsPropertyType;
private Specialization specialization;
- private boolean inliningAllowed = true;
+ private InliningMode inliningMode = InliningMode.NORMAL;
+ private boolean preventDevirtualization = false;
private boolean hasSideEffects = true;
private boolean defaultMethod = false;
@@ -211,11 +213,15 @@
}
public boolean isInliningAllowed() {
- return inliningAllowed;
+ return inliningMode != InliningMode.DO_NOT_INLINE;
}
- public void setInliningAllowed(boolean inliningAllowed) {
- this.inliningAllowed = inliningAllowed;
+ public InliningMode getInliningMode() {
+ return inliningMode;
+ }
+
+ public void setInliningMode(InliningMode inliningMode) {
+ this.inliningMode = inliningMode;
}
public boolean hasSideEffects() {
@@ -234,6 +240,14 @@
return defaultMethod;
}
+ public boolean isDevirtualizationAllowed() {
+ return !preventDevirtualization;
+ }
+
+ public void disallowDevirtualization() {
+ this.preventDevirtualization = true;
+ }
+
/**
* AST representation of @SpecializeMethod.
*/
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 7558c08..391b7ba 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -16,6 +16,7 @@
package com.google.gwt.dev.jjs.ast;
import com.google.gwt.dev.MinimalRebuildCache;
+import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.jjs.Correlation.Literal;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
@@ -291,30 +292,11 @@
private FragmentPartitioningResult fragmentPartitioningResult;
/**
- * Set of method that are pinned and should be skipped by optimizations such as
- * inlining, statification and prunned.
- */
- private Set<JMethod> pinnedMethods = Sets.newHashSet();
-
- /**
- * Returns true if the inliner should try to inline {@code method}.
- */
- public boolean isInliningAllowed(JMethod method) {
- return !pinnedMethods.contains(method) && method.isInliningAllowed();
- }
-
- /**
- * Returns true if {@link MakeCallsStatic} should try to statify {@code method}.
- */
- public boolean isDevitualizationAllowed(JMethod method) {
- return !pinnedMethods.contains(method);
- }
-
- /**
* Add a pinned method.
*/
public void addPinnedMethod(JMethod method) {
- pinnedMethods.add(method);
+ method.setInliningMode(InliningMode.DO_NOT_INLINE);
+ method.disallowDevirtualization();
}
public JProgram(MinimalRebuildCache minimalRebuildCache) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
index 7202c08..3da8a7e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
@@ -267,7 +267,7 @@
JMethod devirtualMethod = new JMethod(sourceInfo, prefix + "__devirtual$",
inClass, method.getType(), false, true, true, AccessModifier.PUBLIC);
// TODO(rluble): DoNotInline should be carried over if 'any' of the targets is marked so.
- devirtualMethod.setInliningAllowed(method.isInliningAllowed());
+ devirtualMethod.setInliningMode(method.getInliningMode());
devirtualMethod.setBody(new JMethodBody(sourceInfo));
devirtualMethod.setSynthetic();
inClass.addMethod(devirtualMethod);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index db07ccc..4722805 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -21,6 +21,7 @@
import com.google.gwt.dev.MinimalRebuildCache;
import com.google.gwt.dev.PrecompileTaskOptions;
import com.google.gwt.dev.cfg.PermutationProperties;
+import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
@@ -742,11 +743,6 @@
private final JsName prototype = objectScope.declareName("prototype");
- // JavaScript functions that arise from methods that were not inlined in the Java AST
- // NOTE: We use a LinkedHashSet to preserve the order of insertion. So that the following passes
- // that use this result are deterministic.
- private final Set<JsNode> functionsForJsInlining = Sets.newLinkedHashSet();
-
{
globalTemp.setObfuscatable(false);
prototype.setObfuscatable(false);
@@ -1238,14 +1234,7 @@
javaMethodForJSFunction.put(jsFunc, x);
- if (!program.isInliningAllowed(x)) {
- jsProgram.disallowInlining(jsFunc);
- }
-
- // Collect the resulting function to be considered by the JsInliner.
- if (methodsForJsInlining.contains(x)) {
- functionsForJsInlining.add(jsFunc);
- }
+ jsFunc.setInliningMode(x.getInliningMode());
List<JsParameter> params = popList(x.getParams().size()); // params
@@ -1391,8 +1380,11 @@
*/
qualifiedMethodName.setQualifier(names.get(method).makeRef(x.getSourceInfo()));
} else {
- // Regular super call. This calls are always static and optimizations normally statify them.
- // They can appear in completely unoptimized code, hence need to be handled here.
+ // These are regular super method call. These calls are always dispatched statically and
+ // optimizations will statify them (except in a few cases, like being target of
+ // {@link Impl.getNameOf}.
+ // For the most part these calls only appear in completely unoptimized code, hence need to
+ // be handled here.
// Construct JCHSU.getPrototypeFor(type).polyname
// TODO(rluble): Ideally we would want to construct the inheritance chain the JS way and
@@ -1403,8 +1395,6 @@
JsInvocation getPrototypeCall = constructInvocation(x.getSourceInfo(),
"JavaClassHierarchySetupUtil.getClassPrototype",
convertJavaLiteral(typeMapper.get(superMethodTargetType)));
- // getClassPrototype is a JSNI call, so we are enabling inlining
- methodsForJsInlining.add(currentMethod);
JsNameRef methodNameRef = polymorphicNames.get(method).makeRef(x.getSourceInfo());
methodNameRef.setQualifier(getPrototypeCall);
@@ -3135,15 +3125,22 @@
}
}
- private class RecordJSInlinableMethods extends JVisitor {
+ private class CollectJsFunctionsForInlining extends JVisitor {
+ // JavaScript functions that arise from methods that were not inlined in the Java AST
+ // NOTE: We use a LinkedHashSet to preserve the order of insertion. So that the following passes
+ // that use this result are deterministic.
+ private Set<JsNode> functionsForJsInlining = Sets.newLinkedHashSet();
private JMethod currentMethod;
@Override
public void endVisit(JMethod x, Context ctx) {
if (x.isNative()) {
- // These are methods that were not considered by the Java method inliner.
- methodsForJsInlining.add(x);
+ // These are methods whose bodies where not traversed by the Java method inliner.
+ JsFunction function = methodBodyMap.get(x.getBody());
+ if (function != null && function.getBody() != null) {
+ functionsForJsInlining.add(function);
+ }
}
currentMethod = null;
@@ -3152,11 +3149,17 @@
@Override
public void endVisit(JMethodCall x, Context ctx) {
JMethod target = x.getTarget();
- if (program.isInliningAllowed(target) && (target.isNative()
- || program.getIndexedTypes().contains(target.getEnclosingType()))) {
- // the currentMethod calls a method that was not considered by the Java MethodInliner; these
- // include JSNI methods and methods whose calls were inserted by normalization passes.
- methodsForJsInlining.add(currentMethod);
+ if (target.isInliningAllowed() && (target.isNative()
+ || program.getIndexedTypes().contains(target.getEnclosingType())
+ || target.getInliningMode() == InliningMode.FORCE_INLINE)) {
+ // These are either: 1) callsites to JSNI functions, in which case MethodInliner did not
+ // attempt to inline; 2) inserted by normalizations passes AFTER all inlining or 3)
+ // calls to methods annotated with @ForceInline that were not inlined by the simple
+ // MethodInliner.
+ JsFunction function = methodBodyMap.get(currentMethod.getBody());
+ if (function != null && function.getBody() != null) {
+ functionsForJsInlining.add(function);
+ }
}
}
@@ -3165,6 +3168,11 @@
currentMethod = x;
return true;
}
+
+ public Set<JsNode> getFunctionsForJsInlining() {
+ accept(program);
+ return functionsForJsInlining;
+ }
}
/**
@@ -3325,12 +3333,6 @@
private Map<String, JsName> indexedFields = Maps.newHashMap();
/**
- * Methods where inlining hasn't happened yet because they are native or contain calls to native
- * methods. See {@link RecordJSInlinableMethods}.
- */
- private Set<JMethod> methodsForJsInlining = Sets.newHashSet();
-
- /**
* Contains JsNames for all interface methods. A special scope is needed so
* that independent classes will obfuscate their interface implementation
* methods the same way.
@@ -3538,7 +3540,6 @@
if (!incremental) {
// TODO(rluble): pull out this analysis and make it a Java AST optimization pass.
new RecordCrossClassCallsAndConstructorLiveness().accept(program);
- new RecordJSInlinableMethods().accept(program);
}
// Map class literals to their respective types.
@@ -3558,7 +3559,10 @@
JavaToJavaScriptMap jjsMap = new JavaToJavaScriptMapImpl(program.getDeclaredTypes(),
names, typeForStatMap, vtableInitForMethodMap);
- return Pair.create(jjsMap, generator.functionsForJsInlining);
+ Set<JsNode> functionsForJsInlining = incremental ? Collections.<JsNode>emptySet() :
+ new CollectJsFunctionsForInlining().getFunctionsForJsInlining();
+
+ return Pair.create(jjsMap, functionsForJsInlining);
}
private JsFunction getJsFunctionFor(JMethod jMethod) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index baaed7e..91c89df 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -16,6 +16,7 @@
package com.google.gwt.dev.jjs.impl;
import com.google.gwt.dev.CompilerContext;
+import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.javac.JSORestrictionsChecker;
import com.google.gwt.dev.javac.JdtUtil;
import com.google.gwt.dev.javac.JsInteropUtil;
@@ -234,6 +235,9 @@
import java.util.Map.Entry;
import java.util.Set;
+import javaemul.internal.annotations.DoNotInline;
+import javaemul.internal.annotations.ForceInline;
+
/**
* Constructs a GWT Java AST from a single isolated compilation unit. The AST is
* not associated with any {@link com.google.gwt.dev.jjs.ast.JProgram} and will
@@ -2811,7 +2815,7 @@
JNewArray valuesArrayCopy = JNewArray.createInitializers(info, enumArrayType, initializers);
if (type.getEnumList().size() > MAX_INLINEABLE_ENUM_SIZE) {
// Only inline values() if it is small.
- method.setInliningAllowed(false);
+ method.setInliningMode(InliningMode.DO_NOT_INLINE);
}
JjsUtils.replaceMethodBody(method, valuesArrayCopy);
}
@@ -4088,17 +4092,19 @@
private void processAnnotations(AbstractMethodDeclaration x,
JMethod method) {
maybeAddMethodSpecialization(x, method);
- maybeSetDoNotInline(x, method);
+ maybeSetInliningMode(x, method);
maybeSetHasNoSideEffects(x, method);
if (isJsInteropEnabled) {
JsInteropUtil.maybeSetJsInteropProperties(method, x.annotations);
}
}
- private void maybeSetDoNotInline(AbstractMethodDeclaration x,
+ private void maybeSetInliningMode(AbstractMethodDeclaration x,
JMethod method) {
- if (JdtUtil.getAnnotation(x.binding, "javaemul.internal.annotations.DoNotInline") != null) {
- method.setInliningAllowed(false);
+ if (JdtUtil.getAnnotation(x.binding, DoNotInline.class.getCanonicalName()) != null) {
+ method.setInliningMode(InliningMode.DO_NOT_INLINE);
+ } else if (JdtUtil.getAnnotation(x.binding, ForceInline.class.getCanonicalName()) != null) {
+ method.setInliningMode(InliningMode.FORCE_INLINE);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index 2e4570b..3c018ee 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -167,7 +167,7 @@
JMethod newMethod =
new JMethod(sourceInfo, newName, enclosingType, returnType, false, true, true, x
.getAccess());
- newMethod.setInliningAllowed(x.isInliningAllowed());
+ newMethod.setInliningMode(x.getInliningMode());
newMethod.setHasSideEffects(x.hasSideEffects());
newMethod.setSynthetic();
newMethod.addThrownExceptions(x.getThrownExceptions());
@@ -279,7 +279,7 @@
return false;
}
- if (!program.isDevitualizationAllowed(method)) {
+ if (!method.isDevirtualizationAllowed()) {
// Method has been specifically excluded from statification.
return false;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index da71a3e..9e09942 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -136,7 +136,7 @@
return InlineResult.BLACKLIST;
}
- if (!program.isInliningAllowed(method)) {
+ if (!method.isInliningAllowed()) {
return InlineResult.BLACKLIST;
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsForceInliningChecker.java b/dev/core/src/com/google/gwt/dev/js/JsForceInliningChecker.java
new file mode 100644
index 0000000..00b215a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsForceInliningChecker.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.common.InliningMode;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+/**
+ * Makes sure methods marked with @ForceInline are actually inlined in optimized mode.
+ */
+public class JsForceInliningChecker {
+ private static final String NAME = JsForceInliningChecker.class.getSimpleName();
+
+ /**
+ * Determines if the evaluation of a JsNode may be affected by side effects.
+ */
+ private static class ForceInliningCheckerVisitor extends JsVisitor {
+
+ private final TreeLogger logger;
+ private boolean error = false;
+ private JavaToJavaScriptMap javaToJavaScriptMap;
+
+ private ForceInliningCheckerVisitor(
+ TreeLogger logger, JavaToJavaScriptMap javaToJavaScriptMap) {
+ this.logger = logger;
+ this.javaToJavaScriptMap = javaToJavaScriptMap;
+ }
+
+ @Override
+ public void endVisit(JsFunction x, JsContext ctx) {
+ if (x.getInliningMode() == InliningMode.FORCE_INLINE) {
+ JMethod originalMethod = javaToJavaScriptMap.nameToMethod(x.getName());
+ String methodName = originalMethod != null ? originalMethod.toString() :
+ x.getName().getShortIdent();
+ logger.log(Type.ERROR, "Function " + methodName
+ + " is marked as @ForceInline but it could not be inlined");
+ error = true;
+ }
+ }
+ }
+
+ /**
+ * Static entry point used by JavaToJavaScriptCompiler.
+ */
+ public static void check(TreeLogger logger, JavaToJavaScriptMap javaToJavaScriptMap,
+ JsProgram program) throws UnableToCompleteException {
+ ForceInliningCheckerVisitor visitor =
+ new ForceInliningCheckerVisitor(logger, javaToJavaScriptMap);
+ visitor.accept(program);
+ if (visitor.error) {
+ throw new UnableToCompleteException();
+ }
+ }
+
+ /**
+ * Utility class.
+ */
+ private JsForceInliningChecker() {
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index 50af027..969cd3c 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.js;
+import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
@@ -776,7 +777,7 @@
return;
}
- if (!program.isInliningAllowed(invokedFunction) || blacklist.contains(invokedFunction)) {
+ if (!invokedFunction.isInliningAllowed() || blacklist.contains(invokedFunction)) {
return;
}
@@ -1056,7 +1057,8 @@
int originalComplexity = complexity(x);
int inlinedComplexity = complexity(op);
double ratio = ((double) inlinedComplexity) / originalComplexity;
- if (ratio > MAX_COMPLEXITY_INCREASE
+ if (invokedFunction.getInliningMode() != InliningMode.FORCE_INLINE
+ && ratio > MAX_COMPLEXITY_INCREASE
&& isInvokedMoreThanOnce(invokedFunction)) {
return x;
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java b/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
index 1fed6b5..f2fc5ca 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
@@ -13,6 +13,7 @@
*/
package com.google.gwt.dev.js.ast;
+import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
@@ -32,6 +33,7 @@
private boolean fromJava;
private JsFunction impliedExecute;
private JsName name;
+ private InliningMode inliningMode = InliningMode.NORMAL;
/**
* Creates an anonymous function.
@@ -93,6 +95,10 @@
return impliedExecute;
}
+ public InliningMode getInliningMode() {
+ return inliningMode;
+ }
+
@Override
public NodeKind getKind() {
return NodeKind.FUNCTION;
@@ -145,6 +151,10 @@
return fromJava;
}
+ public boolean isInliningAllowed() {
+ return inliningMode != InliningMode.DO_NOT_INLINE;
+ }
+
public void setArtificiallyRescued(boolean rescued) {
this.artificiallyRescued = rescued;
}
@@ -165,6 +175,10 @@
this.impliedExecute = impliedExecute;
}
+ public void setInliningMode(InliningMode inliningMode) {
+ this.inliningMode = inliningMode;
+ }
+
public void setName(JsName name) {
this.name = name;
if (name != null) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
index e3894dd..d15f443 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -17,7 +17,6 @@
import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
-import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.util.HashMap;
import java.util.HashSet;
@@ -39,8 +38,6 @@
private final Set<JsFunction> indexedFunctionSet = new HashSet<JsFunction>();
- private final Set<JsFunction> preventInliningOf = Sets.newHashSet();
-
private final JsScope objectScope;
private final JsScope topScope;
@@ -142,14 +139,6 @@
this.indexedFunctionSet.addAll(indexedFunctions.values());
}
- public void disallowInlining(JsFunction function) {
- preventInliningOf.add(function);
- }
-
- public boolean isInliningAllowed(JsFunction function) {
- return !preventInliningOf.contains(function);
- }
-
@Override
public void traverse(JsVisitor v, JsContext ctx) {
if (v.visit(this, ctx)) {
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java
index fa1df30..6ab202b 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/JavaClassHierarchySetupUtil.java
@@ -17,6 +17,8 @@
import com.google.gwt.core.client.JavaScriptObject;
+import javaemul.internal.annotations.ForceInline;
+
/**
* Utility class for defining class prototyes to setup an equivalent to the Java class hierarchy in
* JavaScript.
@@ -209,6 +211,7 @@
static native void emptyMethod() /*-{
}-*/;
+ @ForceInline
static native JavaScriptObject uniqueId(String id) /*-{
return jsinterop.closure.getUniqueId(id);
}-*/;
diff --git a/dev/core/super/javaemul/internal/annotations/ForceInline.java b/dev/core/super/javaemul/internal/annotations/ForceInline.java
new file mode 100644
index 0000000..c0561c2
--- /dev/null
+++ b/dev/core/super/javaemul/internal/annotations/ForceInline.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package javaemul.internal.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation to mark a given method as not inlineable.
+ * <p>
+ * Internal SDK use only, might change or disappear at any time.
+ */
+@CompilerHint
+@Target(ElementType.METHOD)
+public @interface ForceInline {
+}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsDuplicateCaseFolderTest.java b/dev/core/test/com/google/gwt/dev/js/JsDuplicateCaseFolderTest.java
index 5f11f85..bf1412f 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsDuplicateCaseFolderTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsDuplicateCaseFolderTest.java
@@ -84,12 +84,12 @@
private void check(String expected, String input) throws Exception {
// Pass the expected code through the parser to normalize it
- expected = super.optimize(expected, new Class[0]);
+ expected = super.optimizeToSource(expected, new Class[0]);
String output = optimize(input);
assertEquals(expected, output);
}
private String optimize(String js) throws Exception {
- return optimize(js, JsDuplicateCaseFolder.class);
+ return optimizeToSource(js, JsDuplicateCaseFolder.class);
}
}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java b/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java
index 4ca9c21..6772a46 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java
@@ -207,7 +207,7 @@
}
private String optimize(String js) throws Exception {
- return optimize(js, JsSymbolResolver.class,
+ return optimizeToSource(js, JsSymbolResolver.class,
JsDuplicateFunctionRemoverProxy.class);
}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java b/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
index 82b41ad..a6b3f47 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
@@ -15,6 +15,10 @@
*/
package com.google.gwt.dev.js;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.common.InliningMode;
+import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
import com.google.gwt.dev.jjs.impl.OptimizerStats;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsFunction;
@@ -23,6 +27,7 @@
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
@@ -120,7 +125,6 @@
// Always make more than one call, because there are special heuristics for functions that
// are called only once.
- // Inline a devirtualized getter
input = Joiner.on('\n').join(
"function a(o) { $wnd.blah(o); }",
"function f(t,u,b,d) {var a = t; return a.u;}",
@@ -131,6 +135,40 @@
"e({})");
verifyOptimizedObfuscated(expected, input);
}
+
+ public void testInliningAnnotations() throws Exception {
+ String input, expected;
+ // Always make more than one call, because there are special heuristics for functions that
+ // are called only once.
+
+ // Test FORCE_INLINE
+ input = Joiner.on('\n').join(
+ "function uniqueId_forceInline(id) {return jsinterop.closure.getUniqueId(id);}",
+ "function b1() { uniqueId_forceInline('a'); uniqueId_forceInline('b'); } b1();");
+ expected = Joiner.on('\n').join(
+ "function a(){jsinterop.closure.getUniqueId('a');jsinterop.closure.getUniqueId('b')}",
+ "a();");
+ verifyOptimizedObfuscated(expected, input);
+
+ // Test DO_NOT_INLINE
+ input = Joiner.on('\n').join(
+ "function uniqueId_doNotInline(id) {return jsinterop.closure.getUniqueId(id);}",
+ "function b1() { uniqueId_doNotInline('a'); uniqueId_doNotInline('b'); } b1();");
+ expected = Joiner.on('\n').join(
+ "function b(a){return jsinterop.closure.getUniqueId(a)}",
+ "function c(){b('a');b('b')}",
+ "c();");
+ verifyOptimizedObfuscated(expected, input);
+ }
+
+ public void testCheckerError() throws Exception {
+ String input = Joiner.on('\n').join(
+ "function m_forceInline(a,b,c) {a[c]=b;}",
+ "function b1(a) { m_forceInline(a++,a++,a++);} b1();");
+ assertCheckerError(input,
+ "Function m_forceInline is marked as @ForceInline but it could not be inlined");
+ }
+
/**
* A test for mutually-recursive functions. Setup:
*
@@ -335,19 +373,34 @@
}
private void verifyOptimized(String expected, String input) throws Exception {
- String actual = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
+ String actual = optimizeToSource(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
JsInlinerProxy.class, JsUnusedFunctionRemover.class);
- String expectedAfterParse = optimize(expected);
+ String expectedAfterParse = optimizeToSource(expected);
assertEquals(expectedAfterParse, actual);
}
private void verifyOptimizedObfuscated(String expected, String input) throws Exception {
- String actual = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
+ String actual = optimizeToSource(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
JsInlinerProxy.class, JsUnusedFunctionRemover.class, JsObfuscateNamer.class);
- String expectedAfterParse = optimize(expected);
+ String expectedAfterParse = optimizeToSource(expected);
assertEquals(expectedAfterParse, actual);
}
+ private void assertCheckerError(String input, String error) throws Exception {
+ JsProgram optimizedProgram = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
+ JsInlinerProxy.class, JsUnusedFunctionRemover.class);
+ UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+ builder.setLowestLogLevel(TreeLogger.ERROR);
+ builder.expectError(error, null);
+ UnitTestTreeLogger testLogger = builder.createLogger();
+ try {
+ JsForceInliningChecker.check(testLogger, JavaToJavaScriptMap.EMPTY, optimizedProgram);
+ fail("JsForceInliningChecker should have thrown an exception");
+ } catch (UnableToCompleteException expected) {
+ }
+ testLogger.assertCorrectLogEntries();
+ }
+
/**
* A Proxy class to call JsInlner, due to its lack of a single parameter exec method.
*/
@@ -361,6 +414,15 @@
@Override
public void endVisit(JsFunction x, JsContext ctx) {
inlineableFunctions.add(x);
+ JsName functionName = x.getName();
+ if (functionName == null) {
+ return;
+ }
+ if (functionName.getIdent().endsWith("_forceInline")) {
+ x.setInliningMode(InliningMode.FORCE_INLINE);
+ } else if (functionName.getIdent().endsWith("_doNotInline")) {
+ x.setInliningMode(InliningMode.DO_NOT_INLINE);
+ }
}
}.accept(program);
return JsInliner.exec(program, inlineableFunctions);
diff --git a/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java b/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
index d5081a1..3177b09 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
@@ -171,6 +171,6 @@
}
private String optimize(String js) throws Exception {
- return optimize(js, JsStaticEval.class);
+ return optimizeToSource(js, JsStaticEval.class);
}
}
diff --git a/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java
index 9867946..228be8f 100644
--- a/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java
@@ -39,9 +39,27 @@
* @param js the source program
* @param toExec a list of classes that implement
* <code>static void exec(JsProgram)</code>
- * @return optimized JS
+ * @return optimized JS source
*/
- protected String optimize(String js, Class<?>... toExec) throws Exception {
+ protected String optimizeToSource(String js, Class<?>... toExec) throws Exception {
+ JsProgram program = optimize(js, toExec);
+
+ TextOutput text = new DefaultTextOutput(true);
+ JsVisitor generator = new JsSourceGenerationVisitor(text);
+
+ generator.accept(program);
+ return text.toString();
+ }
+
+ /**
+ * Optimize a JS program.
+ *
+ * @param js the source program
+ * @param toExec a list of classes that implement
+ * <code>static void exec(JsProgram)</code>
+ * @return optimized JS program
+ */
+ protected JsProgram optimize(String js, Class<?>... toExec) throws Exception {
JsProgram program = new JsProgram();
List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN,
program.getScope(), new StringReader(js));
@@ -53,10 +71,7 @@
m.invoke(null, program);
}
- TextOutput text = new DefaultTextOutput(true);
- JsVisitor generator = new JsSourceGenerationVisitor(text);
-
- generator.accept(program);
- return text.toString();
+ return program;
}
+
}
\ No newline at end of file