55
55
*/
56
56
class InlineFunctions implements CompilerPass {
57
57
58
- static final DiagnosticType FAILED_REQUIRED_INLINING =
59
- DiagnosticType .error (
60
- "JSC_FAILED_REQUIRED_INLINING" ,
61
- "function {1} annotated @requireInlining could not be inlined here" );
58
+ // NOTE: this diagnostic is only emitted in debug mode and may not cover all inlinings which
59
+ // occur in practice. This allows us to test the pass independently and to obtain information
60
+ // about what inlinings occurred and why.
61
+ static final DiagnosticType MISSED_REQUIRED_INLINING =
62
+ DiagnosticType .warning (
63
+ "JSC_MISSED_REQUIRED_INLINING" ,
64
+ "function annotated @requireInlining could not be inlined on this pass: {0}. Context:"
65
+ + " {1}" );
62
66
63
67
// Note: This pass assumes that all functions are uniquely named variables as enforced by
64
68
// the normalization pass.
@@ -195,8 +199,8 @@ public void findNamedFunctions(NodeTraversal t, Node n, Node parent) {
195
199
}
196
200
197
201
switch (n .getToken ()) {
198
- // Functions expressions in the form of:
199
- // var fooFn = function(x) { return ... }
202
+ // Functions expressions in the form of:
203
+ // var fooFn = function(x) { return ... }
200
204
case VAR :
201
205
case LET :
202
206
case CONST :
@@ -209,8 +213,8 @@ public void findNamedFunctions(NodeTraversal t, Node n, Node parent) {
209
213
}
210
214
break ;
211
215
212
- // Named functions
213
- // function Foo(x) { return ... }
216
+ // Named functions
217
+ // function Foo(x) { return ... }
214
218
case FUNCTION :
215
219
Preconditions .checkState (NodeUtil .isStatementBlock (parent ) || parent .isLabel ());
216
220
if (NodeUtil .isFunctionDeclaration (n )) {
@@ -229,8 +233,8 @@ public void findNamedFunctions(NodeTraversal t, Node n, Node parent) {
229
233
*/
230
234
public void findFunctionExpressions (NodeTraversal t , Node n ) {
231
235
switch (n .getToken ()) {
232
- // Functions expressions in the form of:
233
- // (function(){})();
236
+ // Functions expressions in the form of:
237
+ // (function(){})();
234
238
case OPTCHAIN_CALL :
235
239
case CALL :
236
240
Node fnNode = null ;
@@ -264,9 +268,7 @@ void maybeAddFunction(Function fn, JSChunk chunk) {
264
268
String name = fn .getName ();
265
269
FunctionState functionState = getOrCreateFunctionState (name );
266
270
updateFunctionStateForInlining (fn , chunk , name , functionState );
267
- if (hasRequireInliningAnnotation (fn .getFunctionNode ()) && !functionState .canInline ()) {
268
- compiler .report (JSError .make (fn .getFunctionNode (), FAILED_REQUIRED_INLINING ));
269
- }
271
+ checkState (!(hasRequireInliningAnnotation (fn .getFunctionNode ()) && !functionState .canInline ()));
270
272
}
271
273
272
274
/**
@@ -370,7 +372,7 @@ private boolean hasNoInlineAnnotation(Node fnNode) {
370
372
return jsDocInfo != null && jsDocInfo .isNoInline ();
371
373
}
372
374
373
- private boolean hasRequireInliningAnnotation (Node fnNode ) {
375
+ private static boolean hasRequireInliningAnnotation (Node fnNode ) {
374
376
JSDocInfo jsDocInfo = NodeUtil .getBestJSDocInfo (fnNode );
375
377
return jsDocInfo != null && jsDocInfo .isRequireInlining ();
376
378
}
@@ -435,7 +437,7 @@ private static class CallVisitor extends AbstractPostOrderCallback {
435
437
@ Override
436
438
public void visit (NodeTraversal t , Node n , Node parent ) {
437
439
switch (n .getToken ()) {
438
- // Function calls
440
+ // Function calls
439
441
case OPTCHAIN_CALL :
440
442
case CALL :
441
443
Node child = n .getFirstChild ();
@@ -608,6 +610,18 @@ private void checkNameUsage(Node n, Node parent) {
608
610
// so mark the function as uninlinable.
609
611
// TODO(johnlenz): Should we just remove it from fns here?
610
612
functionState .disallowInlining ();
613
+ } else if ((parent .isAssign ()
614
+ && parent .getJSDocInfo () != null
615
+ && parent .getJSDocInfo ().isConstant ()
616
+ && parent .getSecondChild () == n )
617
+ || (parent .getParent () != null
618
+ && parent .getParent ().isConst ()
619
+ && parent .getFirstChild () == n )) {
620
+ // e.g. const bar = foo; exports.bar = foo;
621
+ // We can't see through this reference right now, but we will be able
622
+ // to once `bar` is inlined; so we shouldn't remove `foo` right now but
623
+ // this is probably okay with @requireInlining.
624
+ functionState .setRemove (false );
611
625
} else {
612
626
// e.g. var fn = bar; <== we can't inline "bar"
613
627
// As this reference can't be inlined mark the function as
@@ -673,9 +687,6 @@ private void trimCandidatesUsingOnCost() {
673
687
Iterator <Entry <String , FunctionState >> i ;
674
688
for (i = fns .entrySet ().iterator (); i .hasNext (); ) {
675
689
FunctionState functionState = i .next ().getValue ();
676
- if (hasRequireInliningAnnotation (functionState .getFn ().getFunctionNode ())) {
677
- continue ;
678
- }
679
690
if (functionState .hasReferences ()) {
680
691
// Only inline function if it decreases the code size.
681
692
boolean lowersCost = minimizeCost (functionState );
@@ -717,13 +728,14 @@ private boolean minimizeCost(FunctionState functionState) {
717
728
* @return Whether inlining the function reduces code size.
718
729
*/
719
730
private boolean inliningLowersCost (FunctionState functionState ) {
720
- return injector .inliningLowersCost (
721
- functionState .getChunk (),
722
- functionState .getFn ().getFunctionNode (),
723
- functionState .getReferences (),
724
- functionState .getNamesToAlias (),
725
- functionState .canRemove (),
726
- functionState .getReferencesThis ());
731
+ return functionState .requireInlining ()
732
+ || injector .inliningLowersCost (
733
+ functionState .getChunk (),
734
+ functionState .getFn ().getFunctionNode (),
735
+ functionState .getReferences (),
736
+ functionState .getNamesToAlias (),
737
+ functionState .canRemove (),
738
+ functionState .getReferencesThis ());
727
739
}
728
740
729
741
/**
@@ -769,14 +781,14 @@ private void resolveInlineConflictsForFunction(FunctionState functionState) {
769
781
Node fnNode = functionState .getFn ().getFunctionNode ();
770
782
Set <String > names = findCalledFunctions (fnNode );
771
783
if (!names .isEmpty ()) {
772
- // Prevent the removal of the referenced functions.
784
+ // Prevent the removal of the referenced functions unless they will be immediately inlined .
773
785
for (String name : names ) {
774
786
FunctionState fsCalled = fns .get (name );
775
787
if (fsCalled != null && fsCalled .canRemove ()) {
776
788
fsCalled .setRemove (false );
777
789
// For functions that can no longer be removed, check if they should
778
790
// still be inlined.
779
- if (!minimizeCost (fsCalled )) {
791
+ if (!minimizeCost (fsCalled ) && ! fsCalled . requireInlining () ) {
780
792
// It can't be inlined remove it from the list.
781
793
fsCalled .disallowInlining ();
782
794
}
@@ -871,6 +883,9 @@ private static class FunctionState {
871
883
private @ Nullable JSChunk chunk = null ;
872
884
private @ Nullable Set <String > namesToAlias = null ;
873
885
886
+ private boolean hasRequireInliningAnnotation = false ;
887
+ private boolean hasRequireInliningAnnotationInitialized = false ;
888
+
874
889
boolean hasExistingFunctionDefinition () {
875
890
return (fn != null );
876
891
}
@@ -931,6 +946,17 @@ public boolean canInline() {
931
946
return inline ;
932
947
}
933
948
949
+ boolean requireInlining () {
950
+ if (fn == null ) {
951
+ return false ;
952
+ }
953
+ if (!hasRequireInliningAnnotationInitialized ) {
954
+ hasRequireInliningAnnotationInitialized = true ;
955
+ hasRequireInliningAnnotation = hasRequireInliningAnnotation (fn .getFunctionNode ());
956
+ }
957
+ return hasRequireInliningAnnotation ;
958
+ }
959
+
934
960
public void disallowInlining () {
935
961
this .inline = false ;
936
962
0 commit comments