-
Notifications
You must be signed in to change notification settings - Fork 371
/
JavaToJavaScriptCompiler.java
1371 lines (1205 loc) · 53.5 KB
/
JavaToJavaScriptCompiler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* 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.jjs;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.SelectionProperty;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.Artifact;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationMetricsArtifact;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.ModuleMetricsArtifact;
import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
import com.google.gwt.core.ext.linker.StatementRanges;
import com.google.gwt.core.ext.linker.SymbolData;
import com.google.gwt.core.ext.linker.SyntheticArtifact;
import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
import com.google.gwt.core.ext.soyc.Range;
import com.google.gwt.core.ext.soyc.SourceMapRecorder;
import com.google.gwt.core.ext.soyc.impl.DependencyRecorder;
import com.google.gwt.core.ext.soyc.impl.SizeMapRecorder;
import com.google.gwt.core.ext.soyc.impl.SplitPointRecorder;
import com.google.gwt.core.ext.soyc.impl.StoryRecorder;
import com.google.gwt.core.linker.SoycReportLinker;
import com.google.gwt.dev.Permutation;
import com.google.gwt.dev.cfg.ConfigurationProperty;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.typemodel.TypeOracle;
import com.google.gwt.dev.jdt.RebindPermutationOracle;
import com.google.gwt.dev.jjs.UnifiedAst.AST;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JBlock;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JGwtCreate;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNode;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReboundEntryPoint;
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.impl.ArrayNormalizer;
import com.google.gwt.dev.jjs.impl.AssertionNormalizer;
import com.google.gwt.dev.jjs.impl.AssertionRemover;
import com.google.gwt.dev.jjs.impl.AstDumper;
import com.google.gwt.dev.jjs.impl.CastNormalizer;
import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
import com.google.gwt.dev.jjs.impl.CodeSplitter;
import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
import com.google.gwt.dev.jjs.impl.CodeSplitter2;
import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
import com.google.gwt.dev.jjs.impl.EnumOrdinalizer;
import com.google.gwt.dev.jjs.impl.EqualityNormalizer;
import com.google.gwt.dev.jjs.impl.Finalizer;
import com.google.gwt.dev.jjs.impl.FixAssignmentToUnbox;
import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields;
import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
import com.google.gwt.dev.jjs.impl.JsAbstractTextTransformer;
import com.google.gwt.dev.jjs.impl.JsFunctionClusterer;
import com.google.gwt.dev.jjs.impl.JsIEBlockTextTransformer;
import com.google.gwt.dev.jjs.impl.JsoDevirtualizer;
import com.google.gwt.dev.jjs.impl.LongCastNormalizer;
import com.google.gwt.dev.jjs.impl.LongEmulationNormalizer;
import com.google.gwt.dev.jjs.impl.MakeCallsStatic;
import com.google.gwt.dev.jjs.impl.MethodCallTightener;
import com.google.gwt.dev.jjs.impl.MethodInliner;
import com.google.gwt.dev.jjs.impl.OptimizerStats;
import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer;
import com.google.gwt.dev.jjs.impl.Pruner;
import com.google.gwt.dev.jjs.impl.RecordRebinds;
import com.google.gwt.dev.jjs.impl.RemoveEmptySuperCalls;
import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides;
import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs;
import com.google.gwt.dev.jjs.impl.ResolveRebinds;
import com.google.gwt.dev.jjs.impl.SameParameterValueOptimizer;
import com.google.gwt.dev.jjs.impl.SourceInfoCorrelator;
import com.google.gwt.dev.jjs.impl.TypeTightener;
import com.google.gwt.dev.jjs.impl.UnifyAst;
import com.google.gwt.dev.jjs.impl.VerifySymbolMap;
import com.google.gwt.dev.jjs.impl.gflow.DataflowOptimizer;
import com.google.gwt.dev.js.BaselineCoverageGatherer;
import com.google.gwt.dev.js.ClosureJsRunner;
import com.google.gwt.dev.js.CoverageInstrumentor;
import com.google.gwt.dev.js.EvalFunctionsAtTopScope;
import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
import com.google.gwt.dev.js.JsCoerceIntShift;
import com.google.gwt.dev.js.JsDuplicateCaseFolder;
import com.google.gwt.dev.js.JsDuplicateFunctionRemover;
import com.google.gwt.dev.js.JsIEBlockSizeVisitor;
import com.google.gwt.dev.js.JsInliner;
import com.google.gwt.dev.js.JsNormalizer;
import com.google.gwt.dev.js.JsObfuscateNamer;
import com.google.gwt.dev.js.JsPrettyNamer;
import com.google.gwt.dev.js.JsReportGenerationVisitor;
import com.google.gwt.dev.js.JsSourceGenerationVisitorWithSizeBreakdown;
import com.google.gwt.dev.js.JsStackEmulator;
import com.google.gwt.dev.js.JsStaticEval;
import com.google.gwt.dev.js.JsStringInterner;
import com.google.gwt.dev.js.JsSymbolResolver;
import com.google.gwt.dev.js.JsUnusedFunctionRemover;
import com.google.gwt.dev.js.JsVerboseNamer;
import com.google.gwt.dev.js.SizeBreakdown;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsForIn;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsLabel;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameOf;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.Memory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.OptionOptimize;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Maps;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.soyc.SoycDashboard;
import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import org.xml.sax.SAXException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.zip.GZIPInputStream;
import javax.xml.parsers.ParserConfigurationException;
/**
* Compiles the Java <code>JProgram</code> representation into its corresponding
* JavaScript source.
*/
public class JavaToJavaScriptCompiler {
private static class PermutationResultImpl implements PermutationResult {
private final ArtifactSet artifacts = new ArtifactSet();
private final byte[][] js;
private final Permutation permutation;
private final byte[] serializedSymbolMap;
private final StatementRanges[] statementRanges;
public PermutationResultImpl(String[] js, Permutation permutation, SymbolData[] symbolMap,
StatementRanges[] statementRanges) {
byte[][] bytes = new byte[js.length][];
for (int i = 0; i < js.length; ++i) {
bytes[i] = Util.getBytes(js[i]);
}
this.js = bytes;
this.permutation = permutation;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Util.writeObjectToStream(baos, (Object) symbolMap);
this.serializedSymbolMap = baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Should never happen with in-memory stream", e);
}
this.statementRanges = statementRanges;
}
@Override
public void addArtifacts(Collection<? extends Artifact<?>> newArtifacts) {
this.artifacts.addAll(newArtifacts);
}
@Override
public ArtifactSet getArtifacts() {
return artifacts;
}
@Override
public byte[][] getJs() {
return js;
}
@Override
public Permutation getPermutation() {
return permutation;
}
@Override
public byte[] getSerializedSymbolMap() {
return serializedSymbolMap;
}
@Override
public StatementRanges[] getStatementRanges() {
return statementRanges;
}
}
private static class TreeStatistics extends JVisitor {
private int nodeCount = 0;
public int getNodeCount() {
return nodeCount;
}
@Override
public boolean visit(JNode x, Context ctx) {
nodeCount++;
return true;
}
}
private static final String ENUM_NAME_OBFUSCATION_PROPERTY = "compiler.enum.obfuscate.names";
/**
* Compiles a particular permutation, based on a precompiled unified AST.
*
* @param logger the logger to use
* @param unifiedAst the result of a
* {@link #precompile(TreeLogger, ModuleDef, RebindPermutationOracle, String[], String[], JJSOptions, boolean, PrecompilationMetricsArtifact)}
* @param permutation the permutation to compile
* @return the output JavaScript
* @throws UnableToCompleteException if an error other than
* {@link OutOfMemoryError} occurs
*/
public static PermutationResult compilePermutation(TreeLogger logger, UnifiedAst unifiedAst,
Permutation permutation) throws UnableToCompleteException {
JJSOptions options = unifiedAst.getOptions();
long startTimeMilliseconds = System.currentTimeMillis();
Event jjsCompilePermutationEvent =
SpeedTracerLogger.start(CompilerEventType.JJS_COMPILE_PERMUTATION, "name", permutation
.prettyPrint());
InternalCompilerException.preload();
PropertyOracle[] propertyOracles = permutation.getPropertyOracles();
int permutationId = permutation.getId();
if (logger.isLoggable(TreeLogger.INFO)) {
logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "...");
}
long permStart = System.currentTimeMillis();
try {
if (JProgram.isTracingEnabled()) {
System.out.println("------------------------------------------------------------");
System.out.println("| (new permuation) |");
System.out.println("------------------------------------------------------------");
System.out.println("Properties: " + permutation.prettyPrint());
}
AST ast = unifiedAst.getFreshAst();
JProgram jprogram = ast.getJProgram();
JsProgram jsProgram = ast.getJsProgram();
Map<StandardSymbolData, JsName> symbolTable =
new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator());
ResolveRebinds.exec(jprogram, permutation.getOrderedRebindAnswers());
// Traverse the AST to figure out which lines are instrumentable for
// coverage. This has to happen before optimizations because functions might
// be optimized out; we want those marked as "not executed", not "not
// instrumentable".
Multimap<String, Integer> instrumentableLines = BaselineCoverageGatherer.exec(jprogram);
// (4) Optimize the normalized Java AST for each permutation.
int optimizationLevel = options.getOptimizationLevel();
if (optimizationLevel == OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
draftOptimize(jprogram);
} else {
optimize(options, jprogram);
}
RemoveEmptySuperCalls.exec(jprogram);
// (5) "Normalize" the high-level Java tree into a lower-level tree more
// suited for JavaScript code generation. Don't go reordering these
// willy-nilly because there are some subtle interdependencies.
JsoDevirtualizer.exec(jprogram);
CatchBlockNormalizer.exec(jprogram);
PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
LongCastNormalizer.exec(jprogram);
LongEmulationNormalizer.exec(jprogram);
CastNormalizer.exec(jprogram, options.isCastCheckingDisabled());
ArrayNormalizer.exec(jprogram);
EqualityNormalizer.exec(jprogram);
// (6) Perform further post-normalization optimizations
// Prune everything
Pruner.exec(jprogram, false);
// prune all Object.getClass() overrides and replace with inline field ref
ReplaceGetClassOverrides.exec(jprogram);
// (7) Generate a JavaScript code DOM from the Java type declarations
jprogram.typeOracle.recomputeAfterOptimizations();
JavaToJavaScriptMap jjsmap =
GenerateJavaScriptAST.exec(jprogram, jsProgram, options.getOutput(), symbolTable,
propertyOracles);
// (8) Normalize the JS AST.
// Fix invalid constructs created during JS AST gen.
JsNormalizer.exec(jsProgram);
// Resolve all unresolved JsNameRefs.
JsSymbolResolver.exec(jsProgram);
// Move all function definitions to a top-level scope, to reduce weirdness
EvalFunctionsAtTopScope.exec(jsProgram, jjsmap);
// (9) Optimize the JS AST.
if (optimizationLevel > OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
optimizeJs(options, jsProgram);
/*
* Coalesce redundant labels in switch statements.
*/
JsDuplicateCaseFolder.exec(jsProgram);
}
/*
* Creates new variables, must run before code splitter and namer.
*/
JsStackEmulator.exec(jprogram, jsProgram, propertyOracles, jjsmap);
/*
* If coverage is enabled, instrument the AST to record location info.
*/
CoverageInstrumentor.exec(jsProgram, instrumentableLines);
/*
* Work around Safari 5 bug by rewriting a >> b as ~~a >> b.
*
* No shifts may be generated after this point.
*/
JsCoerceIntShift.exec(jsProgram, logger, propertyOracles);
// (10) Split up the program into fragments
SyntheticArtifact dependencies = null;
if (options.isRunAsyncEnabled()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int fragmentsMerge = 0;
int expectedFragmentCount = options.getFragmentCount();
if (expectedFragmentCount > 0) {
// + 1 for left over, + 1 for initial gave us the total number
// of fragments without splitting.
fragmentsMerge = jprogram.getRunAsyncs().size() + 2 - expectedFragmentCount;
} else {
fragmentsMerge = options.getFragmentsMerge();
}
// Pick and choose which code splitter to use. Only use the experimental
// one when the user explicitly decides the project needs fragment
// merging.
if (fragmentsMerge > 0) {
CodeSplitter2.exec(logger, jprogram, jsProgram, jjsmap, fragmentsMerge,
chooseDependencyRecorder(options.isSoycEnabled(), baos));
} else {
CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, chooseDependencyRecorder(options
.isSoycEnabled(), baos));
}
if (baos.size() == 0 && options.isSoycEnabled()) {
recordNonSplitDependencies(jprogram, baos);
}
if (baos.size() > 0) {
dependencies =
new SyntheticArtifact(SoycReportLinker.class, "dependencies" + permutationId
+ ".xml.gz", baos.toByteArray());
}
}
// detect if browser is ie6 or not known
boolean isIE6orUnknown = findBooleanProperty(propertyOracles, logger, "user.agent", "ie6",
true, false, true);
boolean isSourceMapsEnabled = findBooleanProperty(propertyOracles, logger,
"compiler.useSourceMaps", "true", true, false, false);
// (10.5) Obfuscate
Map<JsName, String> obfuscateMap = Maps.create();
switch (options.getOutput()) {
case OBFUSCATED:
obfuscateMap = JsStringInterner.exec(jprogram, jsProgram, isIE6orUnknown);
JsObfuscateNamer.exec(jsProgram);
if (options.isAggressivelyOptimize()) {
if (JsStackEmulator.getStackMode(propertyOracles) == JsStackEmulator.StackMode.STRIP) {
boolean changed = false;
for (int i = 0; i < jsProgram.getFragmentCount(); i++) {
JsBlock fragment = jsProgram.getFragmentBlock(i);
changed = JsDuplicateFunctionRemover.exec(jsProgram, fragment) || changed;
}
if (changed) {
JsUnusedFunctionRemover.exec(jsProgram);
// run again
JsObfuscateNamer.exec(jsProgram);
}
}
}
break;
case PRETTY:
// We don't intern strings in pretty mode to imprmakeSouove readability
JsPrettyNamer.exec(jsProgram);
break;
case DETAILED:
obfuscateMap = JsStringInterner.exec(jprogram, jsProgram, isIE6orUnknown);
JsVerboseNamer.exec(jsProgram);
break;
default:
throw new InternalCompilerException("Unknown output mode");
}
// (10.8) Handle cross-island references.
// No new JsNames or references to JSNames can be introduced after this
// point.
HandleCrossFragmentReferences.exec(logger, jsProgram, propertyOracles);
// Verify that SymbolMap is somewhat close to being complete.
VerifySymbolMap.exec(jsProgram, jjsmap, symbolTable);
// (11) Perform any post-obfuscation normalizations.
// Work around an IE7 bug,
// http://code.google.com/p/google-web-toolkit/issues/detail?id=1440
// note, JsIEBlockTextTransformer now handles restructuring top level
// blocks, this class now handles non-top level blocks only.
boolean splitBlocks = isIE6orUnknown;
if (splitBlocks) {
JsIEBlockSizeVisitor.exec(jsProgram);
}
JsBreakUpLargeVarStatements.exec(jsProgram, propertyOracles);
// (12) Generate the final output text.
String[] js = new String[jsProgram.getFragmentCount()];
StatementRanges[] ranges = new StatementRanges[js.length];
SizeBreakdown[] sizeBreakdowns =
options.isSoycEnabled() || options.isCompilerMetricsEnabled()
? new SizeBreakdown[js.length] : null;
List<Map<Range, SourceInfo>> sourceInfoMaps = new ArrayList<Map<Range, SourceInfo>>();
generateJavaScriptCode(options, jprogram, jsProgram, jjsmap, js, ranges,
sizeBreakdowns, sourceInfoMaps, splitBlocks, isSourceMapsEnabled);
PermutationResult toReturn =
new PermutationResultImpl(js, permutation, makeSymbolMap(symbolTable, jsProgram), ranges);
CompilationMetricsArtifact compilationMetrics = null;
// TODO: enable this when ClosureCompiler is enabled
if (!options.isClosureCompilerEnabled() && options.isCompilerMetricsEnabled()) {
compilationMetrics = new CompilationMetricsArtifact(permutation.getId());
compilationMetrics.setCompileElapsedMilliseconds(System.currentTimeMillis()
- startTimeMilliseconds);
compilationMetrics.setElapsedMilliseconds(System.currentTimeMillis()
- ManagementFactory.getRuntimeMXBean().getStartTime());
compilationMetrics.setJsSize(sizeBreakdowns);
compilationMetrics.setPermutationDescription(permutation.prettyPrint());
toReturn.addArtifacts(Lists.create(unifiedAst.getModuleMetrics(), unifiedAst
.getPrecompilationMetrics(), compilationMetrics));
}
// TODO: enable this when ClosureCompiler is enabled
if (!options.isClosureCompilerEnabled()) {
toReturn.addArtifacts(makeSoycArtifacts(logger, permutationId, jprogram, js, sizeBreakdowns,
options.isSoycExtra() ? sourceInfoMaps : null, dependencies, jjsmap, obfuscateMap,
unifiedAst.getModuleMetrics(), unifiedAst.getPrecompilationMetrics(), compilationMetrics,
options.isSoycHtmlDisabled()));
}
// TODO: enable this when ClosureCompiler is enabled
if (!options.isClosureCompilerEnabled() && isSourceMapsEnabled) {
logger.log(TreeLogger.INFO, "Source Maps Enabled");
toReturn.addArtifacts(SourceMapRecorder.makeSourceMapArtifacts(sourceInfoMaps,
permutationId));
}
logTrackingStats(logger);
if (logger.isLoggable(TreeLogger.TRACE)) {
logger.log(TreeLogger.TRACE, "Permutation took " + (System.currentTimeMillis() - permStart)
+ " ms");
}
return toReturn;
} catch (Throwable e) {
throw CompilationProblemReporter.logAndTranslateException(logger, e);
} finally {
jjsCompilePermutationEvent.end();
}
}
/**
* Look for a selection property in all property oracles.
*/
public static boolean findBooleanProperty(PropertyOracle[] propertyOracles, TreeLogger logger,
String name, String valueToFind, boolean valueIfFound, boolean valueIfNotFound,
boolean valueIfError) {
boolean toReturn = valueIfNotFound;
for (PropertyOracle oracle : propertyOracles) {
try {
SelectionProperty property = oracle.getSelectionProperty(logger, name);
if (valueToFind.equals(property.getCurrentValue())) {
toReturn = valueIfFound;
break;
}
} catch (BadPropertyValueException e) {
// unknown value play it safe
toReturn = valueIfError;
break;
}
}
return toReturn;
}
public static UnifiedAst precompile(TreeLogger logger, ModuleDef module,
RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes,
JJSOptions options, boolean singlePermutation) throws UnableToCompleteException {
return precompile(logger, module, rpo, declEntryPts, additionalRootTypes, options,
singlePermutation, null);
}
/**
* Performs a precompilation, returning a unified AST.
*
* @param logger the logger to use
* @param module the module to compile
* @param rpo the RebindPermutationOracle
* @param declEntryPts the set of entry classes declared in a GWT module;
* these will be automatically rebound
* @param additionalRootTypes additional classes that should serve as code
* roots; will not be rebound; may be <code>null</code>
* @param options the compiler options
* @param singlePermutation if true, do not pre-optimize the resulting AST or
* allow serialization of the result
* @param precompilationMetrics if not null, gather diagnostic information
* from this build for a report.
* @return the unified AST used to drive permutation compiles
* @throws UnableToCompleteException if an error other than
* {@link OutOfMemoryError} occurs
*/
public static UnifiedAst precompile(TreeLogger logger, ModuleDef module,
RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes,
JJSOptions options, boolean singlePermutation,
PrecompilationMetricsArtifact precompilationMetrics) throws UnableToCompleteException {
InternalCompilerException.preload();
if (additionalRootTypes == null) {
additionalRootTypes = Empty.STRINGS;
}
if (declEntryPts.length + additionalRootTypes.length == 0) {
throw new IllegalArgumentException("entry point(s) required");
}
Set<String> allRootTypes = new TreeSet<String>();
// Find all the possible rebinds for declared entry point types.
for (String element : declEntryPts) {
String[] all = rpo.getAllPossibleRebindAnswers(logger, element);
Collections.addAll(allRootTypes, all);
}
rpo.getGeneratorContext().finish(logger);
Collections.addAll(allRootTypes, additionalRootTypes);
allRootTypes.addAll(JProgram.CODEGEN_TYPES_SET);
allRootTypes.addAll(JProgram.INDEX_TYPES_SET);
/*
* Add all SingleJsoImpl types that we know about. It's likely that the
* concrete types are never explicitly referenced.
*/
TypeOracle typeOracle = rpo.getCompilationState().getTypeOracle();
for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoIntf : typeOracle
.getSingleJsoImplInterfaces()) {
allRootTypes.add(typeOracle.getSingleJsoImpl(singleJsoIntf).getQualifiedSourceName());
}
Memory.maybeDumpMemory("CompStateBuilt");
JProgram jprogram = new JProgram();
JsProgram jsProgram = new JsProgram();
try {
// (2) Assemble the Java AST.
UnifyAst unifyAst = new UnifyAst(jprogram, jsProgram, options, rpo);
unifyAst.addRootTypes(allRootTypes);
// TODO: move this into UnifyAst?
findEntryPoints(logger, rpo, declEntryPts, jprogram);
unifyAst.exec(logger);
List<String> finalTypeOracleTypes = Lists.create();
if (precompilationMetrics != null) {
for (com.google.gwt.core.ext.typeinfo.JClassType type : typeOracle.getTypes()) {
finalTypeOracleTypes =
Lists.add(finalTypeOracleTypes, type.getPackage().getName() + "." + type.getName());
}
precompilationMetrics.setFinalTypeOracleTypes(finalTypeOracleTypes);
}
// Free up memory.
rpo.clear();
if (options.isSoycEnabled()) {
SourceInfoCorrelator.exec(jprogram);
}
// Compute all super type/sub type info
jprogram.typeOracle.computeBeforeAST();
Memory.maybeDumpMemory("AstOnly");
AstDumper.maybeDumpAST(jprogram);
// See if we should run the EnumNameObfuscator
if (module != null) {
ConfigurationProperty enumNameObfuscationProp =
(ConfigurationProperty) module.getProperties().find(ENUM_NAME_OBFUSCATION_PROPERTY);
if (enumNameObfuscationProp != null
&& Boolean.parseBoolean(enumNameObfuscationProp.getValue())) {
EnumNameObfuscator.exec(jprogram, logger);
}
}
// (3) Perform Java AST normalizations.
FixAssignmentToUnbox.exec(jprogram);
/*
* TODO: If we defer this until later, we could maybe use the results of
* the assertions to enable more optimizations.
*/
if (options.isEnableAssertions()) {
// Turn into assertion checking calls.
AssertionNormalizer.exec(jprogram);
} else {
// Remove all assert statements.
AssertionRemover.exec(jprogram);
}
// Fix up GWT.runAsync()
if (module != null && options.isRunAsyncEnabled()) {
ReplaceRunAsyncs.exec(logger, jprogram);
CodeSplitter2.pickInitialLoadSequence(logger, jprogram, module.getProperties());
}
ImplementClassLiteralsAsFields.exec(jprogram);
/*
* 4) Possibly optimize some.
*
* Don't optimize early if this is a draft compile, or if there's only one
* permutation.
*/
if (options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT
&& !singlePermutation) {
if (options.isOptimizePrecompile()) {
/*
* Go ahead and optimize early, so that each permutation will run
* faster. This code path is used by the Compiler entry point. We
* assume that we will not be able to perfectly parallelize the
* permutation compiles, so let's optimize as much as possible the
* common AST. In some cases, this might also have the side benefit of
* reducing the total permutation count.
*/
optimize(options, jprogram);
} else {
/*
* Do only minimal early optimizations. This code path is used by the
* Precompile entry point. The external system might be able to
* perfectly parallelize the permutation compiles, so let's avoid
* doing potentially superlinear optimizations on the unified AST.
*/
optimizeLoop("Early Optimization", jprogram, false);
}
}
Set<String> rebindRequests = new HashSet<String>();
RecordRebinds.exec(jprogram, rebindRequests);
if (options.isCompilerMetricsEnabled()) {
precompilationMetrics.setAstTypes(getReferencedJavaClasses(jprogram));
}
logTrackingStats(logger);
Event createUnifiedAstEvent = SpeedTracerLogger.start(CompilerEventType.CREATE_UNIFIED_AST);
UnifiedAst result =
new UnifiedAst(options, new AST(jprogram, jsProgram), singlePermutation, rebindRequests);
createUnifiedAstEvent.end();
return result;
} catch (Throwable e) {
throw CompilationProblemReporter.logAndTranslateException(logger, e);
} finally {
}
}
/**
* Perform the minimal amount of optimization to make sure the compile
* succeeds.
*/
protected static void draftOptimize(JProgram jprogram) {
Event draftOptimizeEvent = SpeedTracerLogger.start(CompilerEventType.DRAFT_OPTIMIZE);
Finalizer.exec(jprogram);
MakeCallsStatic.exec(jprogram);
jprogram.typeOracle.recomputeAfterOptimizations();
// needed for certain libraries that depend on dead stripping to work
DeadCodeElimination.exec(jprogram);
Pruner.exec(jprogram, true);
jprogram.typeOracle.recomputeAfterOptimizations();
draftOptimizeEvent.end();
}
protected static void optimize(JJSOptions options, JProgram jprogram) throws InterruptedException {
Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE);
List<OptimizerStats> allOptimizerStats = new ArrayList<OptimizerStats>();
int counter = 0;
int optimizationLevel = options.getOptimizationLevel();
while (true) {
counter++;
if (optimizationLevel < OptionOptimize.OPTIMIZE_LEVEL_MAX && counter > optimizationLevel) {
break;
}
if (Thread.interrupted()) {
optimizeEvent.end();
throw new InterruptedException();
}
AstDumper.maybeDumpAST(jprogram);
OptimizerStats stats =
optimizeLoop("Pass " + counter, jprogram, options.isAggressivelyOptimize());
allOptimizerStats.add(stats);
if (!stats.didChange()) {
break;
}
}
if (options.isAggressivelyOptimize()) {
// Just run it once, because it is very time consuming
allOptimizerStats.add(DataflowOptimizer.exec(jprogram));
}
if (JProgram.isTracingEnabled()) {
System.out.println("");
System.out.println(" Java Optimization Stats");
System.out.println("");
for (OptimizerStats stats : allOptimizerStats) {
System.out.println(stats.prettyPrint());
}
}
optimizeEvent.end();
}
protected static void optimizeJs(JJSOptions options, JsProgram jsProgram)
throws InterruptedException {
List<OptimizerStats> allOptimizerStats = new ArrayList<OptimizerStats>();
int counter = 0;
while (true) {
counter++;
if (Thread.interrupted()) {
throw new InterruptedException();
}
Event optimizeJsEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE_JS);
OptimizerStats stats = new OptimizerStats("Pass " + counter);
// Remove unused functions, possible
stats.add(JsStaticEval.exec(jsProgram));
// Inline JavaScript function invocations
stats.add(JsInliner.exec(jsProgram));
// Remove unused functions, possible
stats.add(JsUnusedFunctionRemover.exec(jsProgram));
// Save the stats to print out after optimizers finish.
allOptimizerStats.add(stats);
optimizeJsEvent.end();
int optimizationLevel = options.getOptimizationLevel();
if ((optimizationLevel < OptionOptimize.OPTIMIZE_LEVEL_MAX && counter > optimizationLevel)
|| !stats.didChange()) {
break;
}
}
if (JProgram.isTracingEnabled()) {
System.out.println("");
System.out.println(" JavaScript Optimization Stats");
System.out.println("");
for (OptimizerStats stats : allOptimizerStats) {
System.out.println(stats.prettyPrint());
}
}
}
protected static OptimizerStats optimizeLoop(String passName, JProgram jprogram,
boolean isAggressivelyOptimize) {
Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "loop");
// Count the number of nodes in the AST so we can measure the efficiency of
// the optimizers.
Event countEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "countNodes");
TreeStatistics treeStats = new TreeStatistics();
treeStats.accept(jprogram);
int numNodes = treeStats.getNodeCount();
countEvent.end();
// Recompute clinits each time, they can become empty.
jprogram.typeOracle.recomputeAfterOptimizations();
// jprogram.methodOracle =
// MethodOracleBuilder.buildMethodOracle(jprogram);
OptimizerStats stats = new OptimizerStats(passName);
// Remove unreferenced types, fields, methods, [params, locals]
stats.add(Pruner.exec(jprogram, true).recordVisits(numNodes));
// finalize locals, params, fields, methods, classes
stats.add(Finalizer.exec(jprogram).recordVisits(numNodes));
// rewrite non-polymorphic calls as static calls; update all call sites
stats.add(MakeCallsStatic.exec(jprogram).recordVisits(numNodes));
// type flow tightening
// - fields, locals based on assignment
// - params based on assignment and call sites
// - method bodies based on return statements
// - polymorphic methods based on return types of all implementors
// - optimize casts and instance of
stats.add(TypeTightener.exec(jprogram).recordVisits(numNodes));
// tighten method call bindings
stats.add(MethodCallTightener.exec(jprogram).recordVisits(numNodes));
// dead code removal??
stats.add(DeadCodeElimination.exec(jprogram).recordVisits(numNodes));
// inlining
stats.add(MethodInliner.exec(jprogram).recordVisits(numNodes));
if (isAggressivelyOptimize) {
// remove same parameters value
stats.add(SameParameterValueOptimizer.exec(jprogram).recordVisits(numNodes));
/*
* Enum ordinalization.
*
* TODO(jbrosenberg): graduate this out of the 'isAggressivelyOptimize'
* block, over time.
*/
stats.add(EnumOrdinalizer.exec(jprogram).recordVisits(numNodes));
}
// prove that any types that have been culled from the main tree are
// unreferenced due to type tightening?
optimizeEvent.end();
return stats;
}
private static MultipleDependencyGraphRecorder chooseDependencyRecorder(boolean soycEnabled,
OutputStream out) {
MultipleDependencyGraphRecorder dependencyRecorder = CodeSplitter.NULL_RECORDER;
if (soycEnabled) {
dependencyRecorder = new DependencyRecorder(out);
}
return dependencyRecorder;
}
private static JMethodCall createReboundModuleLoad(TreeLogger logger, SourceInfo info,
JDeclaredType reboundEntryType, String originalMainClassName, JDeclaredType enclosingType)
throws UnableToCompleteException {
if (!(reboundEntryType instanceof JClassType)) {
logger.log(TreeLogger.ERROR, "Module entry point class '" + originalMainClassName
+ "' must be a class", null);
throw new UnableToCompleteException();
}
JClassType entryClass = (JClassType) reboundEntryType;
if (entryClass.isAbstract()) {
logger.log(TreeLogger.ERROR, "Module entry point class '" + originalMainClassName
+ "' must not be abstract", null);
throw new UnableToCompleteException();
}
JMethod entryMethod = findMainMethodRecurse(entryClass);
if (entryMethod == null) {
logger.log(TreeLogger.ERROR,
"Could not find entry method 'onModuleLoad()' method in entry point class '"
+ originalMainClassName + "'", null);
throw new UnableToCompleteException();
}
if (entryMethod.isAbstract()) {
logger.log(TreeLogger.ERROR, "Entry method 'onModuleLoad' in entry point class '"
+ originalMainClassName + "' must not be abstract", null);
throw new UnableToCompleteException();
}
JExpression qualifier = null;
if (!entryMethod.isStatic()) {
qualifier = JGwtCreate.createInstantiationExpression(info, entryClass, enclosingType);
if (qualifier == null) {
logger.log(TreeLogger.ERROR,
"No default (zero argument) constructor could be found in entry point class '"
+ originalMainClassName
+ "' to qualify a call to non-static entry method 'onModuleLoad'", null);
throw new UnableToCompleteException();
}
}
return new JMethodCall(info, qualifier, entryMethod);
}
private static void findEntryPoints(TreeLogger logger, RebindPermutationOracle rpo,
String[] mainClassNames, JProgram program) throws UnableToCompleteException {
Event findEntryPointsEvent = SpeedTracerLogger.start(CompilerEventType.FIND_ENTRY_POINTS);
JMethod bootStrapMethod = program.getIndexedMethod("EntryMethodHolder.init");
JMethodBody body = (JMethodBody) bootStrapMethod.getBody();
JBlock block = body.getBlock();
SourceInfo info = block.getSourceInfo().makeChild();
// Also remember $entry, which we'll handle specially in GenerateJsAst
JMethod registerEntry = program.getIndexedMethod("Impl.registerEntry");
program.addEntryMethod(registerEntry);
for (String mainClassName : mainClassNames) {
block.addStmt(makeStatsCalls(info, program, mainClassName));
JDeclaredType mainType = program.getFromTypeMap(mainClassName);
if (mainType == null) {
logger.log(TreeLogger.ERROR, "Could not find module entry point class '" + mainClassName
+ "'", null);
throw new UnableToCompleteException();
}
JMethod mainMethod = findMainMethod(mainType);
if (mainMethod != null && mainMethod.isStatic()) {
JMethodCall onModuleLoadCall = new JMethodCall(info, null, mainMethod);
block.addStmt(onModuleLoadCall.makeStatement());
continue;
}
// Couldn't find a static main method; must rebind the class
String[] resultTypeNames = rpo.getAllPossibleRebindAnswers(logger, mainClassName);
List<JClassType> resultTypes = new ArrayList<JClassType>();
List<JExpression> entryCalls = new ArrayList<JExpression>();
for (String resultTypeName : resultTypeNames) {
JDeclaredType resultType = program.getFromTypeMap(resultTypeName);
if (resultType == null) {
logger.log(TreeLogger.ERROR, "Could not find module entry point class '" + resultTypeName
+ "' after rebinding from '" + mainClassName + "'", null);
throw new UnableToCompleteException();
}
JMethodCall onModuleLoadCall =
createReboundModuleLoad(logger, info, resultType, mainClassName, bootStrapMethod
.getEnclosingType());
resultTypes.add((JClassType) resultType);
entryCalls.add(onModuleLoadCall);
}
if (resultTypes.size() == 1) {
block.addStmt(entryCalls.get(0).makeStatement());
} else {
JReboundEntryPoint reboundEntryPoint =
new JReboundEntryPoint(info, mainType, resultTypes, entryCalls);
block.addStmt(reboundEntryPoint);
}
}
program.addEntryMethod(bootStrapMethod);
findEntryPointsEvent.end();
}
private static JMethod findMainMethod(JDeclaredType declaredType) {