Skip to content

Commit

Permalink
Add an options.assumeAccurateNullUndefinedTypes to gate a local type-…
Browse files Browse the repository at this point in the history
…based optimization

Before, PeepholeMinimizeConditions is folding "if (x === null)" into "if (x)" if it thinks "x" can't be undefined from its type. This is not always sound. For example:

/** @type {!Object<string, string>} */
var map = {};
var x = map['foo']; // x can be undefined at runtime, but the compiler can't prove it
if (x === null) {
  // Code relying on the difference between null and undefined can break
}

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=141925759
  • Loading branch information
Dominator008 authored and blickly committed Dec 14, 2016
1 parent ee6f16f commit 14b6188
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 15 deletions.
8 changes: 8 additions & 0 deletions src/com/google/javascript/jscomp/CommandLineRunner.java
Expand Up @@ -410,6 +410,12 @@ private static class Flags {
+ "may result in incorrect results.")
private boolean useTypesForOptimization = true;

@Option(name = "--assume_accurate_null_undefined_types",
handler = BooleanOptionHandler.class,
usage = "Enables optimizations which rely on accurate distinction between null and "
+ "undefined types.")
private boolean assumeAccurateNullUndefinedTypes = false;

@Option(name = "--assume_function_wrapper",
handler = BooleanOptionHandler.class,
usage = "Enable additional optimizations based on the assumption that the output will be "
Expand Down Expand Up @@ -1562,6 +1568,8 @@ protected CompilerOptions createOptions() {
level.setTypeBasedOptimizationOptions(options);
}

options.setAssumeAccurateNullUndefinedTypes(flags.assumeAccurateNullUndefinedTypes);

if (flags.assumeFunctionWrapper) {
level.setWrappedOutputOptimizations(options);
}
Expand Down
1 change: 1 addition & 0 deletions src/com/google/javascript/jscomp/Compiler.java
Expand Up @@ -337,6 +337,7 @@ public void initOptions(CompilerOptions options) {
options.setInlineProperties(false);
options.setUseTypesForLocalOptimization(false);
options.setUseTypesForOptimization(false);
options.setAssumeAccurateNullUndefinedTypes(false);
}

if (options.legacyCodeCompile) {
Expand Down
10 changes: 10 additions & 0 deletions src/com/google/javascript/jscomp/CompilerOptions.java
Expand Up @@ -525,6 +525,12 @@ public enum ExtractPrototypeMemberDeclarationsMode {
/** Use type information to enable additional optimization opportunities. */
boolean useTypesForLocalOptimization;

/**
* Assume accurate distinction between null and undefined to enable additional optimization
* opportunities.
*/
boolean assumeAccurateNullUndefinedTypes;

//--------------------------------
// Renaming
//--------------------------------
Expand Down Expand Up @@ -2188,6 +2194,10 @@ public void setUseTypesForLocalOptimization(boolean useTypesForLocalOptimization
this.useTypesForLocalOptimization = useTypesForLocalOptimization;
}

public void setAssumeAccurateNullUndefinedTypes(boolean assumeAccurateNullUndefinedTypes) {
this.assumeAccurateNullUndefinedTypes = assumeAccurateNullUndefinedTypes;
}

@Deprecated
public void setUseTypesForOptimization(boolean useTypesForOptimization) {
if (useTypesForOptimization) {
Expand Down
9 changes: 6 additions & 3 deletions src/com/google/javascript/jscomp/DefaultPassConfig.java
Expand Up @@ -1543,10 +1543,12 @@ protected CompilerPass create(AbstractCompiler compiler) {
/** Various peephole optimizations. */
private static CompilerPass createPeepholeOptimizationsPass(AbstractCompiler compiler) {
final boolean late = false;
final boolean useTypesForOptimization = compiler.getOptions().useTypesForLocalOptimization;
CompilerOptions options = compiler.getOptions();
final boolean useTypesForOptimization = options.useTypesForLocalOptimization;
return new PeepholeOptimizationsPass(compiler,
new MinimizeExitPoints(compiler),
new PeepholeMinimizeConditions(late, useTypesForOptimization),
new PeepholeMinimizeConditions(
late, useTypesForOptimization, options.assumeAccurateNullUndefinedTypes),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeReplaceKnownMethods(late, useTypesForOptimization),
new PeepholeRemoveDeadCode(),
Expand Down Expand Up @@ -1582,7 +1584,8 @@ protected CompilerPass create(AbstractCompiler compiler) {
return new PeepholeOptimizationsPass(compiler,
new StatementFusion(options.aggressiveFusion),
new PeepholeRemoveDeadCode(),
new PeepholeMinimizeConditions(late, useTypesForOptimization),
new PeepholeMinimizeConditions(
late, useTypesForOptimization, options.assumeAccurateNullUndefinedTypes),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeReplaceKnownMethods(late, useTypesForOptimization),
new PeepholeFoldConstants(late, useTypesForOptimization),
Expand Down
6 changes: 4 additions & 2 deletions src/com/google/javascript/jscomp/ExpandJqueryAliases.java
Expand Up @@ -83,9 +83,11 @@ class ExpandJqueryAliases extends AbstractPostOrderCallback
// These passes should make the code easier to analyze.
// Passes, such as StatementFusion, are omitted for this reason.
final boolean late = false;
boolean useTypesForOptimization = compiler.getOptions().useTypesForLocalOptimization;
CompilerOptions options = compiler.getOptions();
boolean useTypesForOptimization = options.useTypesForLocalOptimization;
this.peepholePasses = new PeepholeOptimizationsPass(compiler,
new PeepholeMinimizeConditions(late, useTypesForOptimization),
new PeepholeMinimizeConditions(
late, useTypesForOptimization, options.assumeAccurateNullUndefinedTypes),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeReplaceKnownMethods(late, useTypesForOptimization),
new PeepholeRemoveDeadCode(),
Expand Down
16 changes: 10 additions & 6 deletions src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java
Expand Up @@ -40,6 +40,7 @@ class PeepholeMinimizeConditions

private final boolean late;
private final boolean useTypes;
private final boolean assumeAccurateNullUndefinedTypes;

/**
* @param late When late is false, this mean we are currently running before
Expand All @@ -48,9 +49,11 @@ class PeepholeMinimizeConditions
* merging statements with commas, etc). When this is true, we would
* do anything to minimize for size.
*/
PeepholeMinimizeConditions(boolean late, boolean useTypes) {
PeepholeMinimizeConditions(boolean late, boolean useTypes,
boolean assumeAccurateNullUndefinedTypes) {
this.late = late;
this.useTypes = useTypes;
this.assumeAccurateNullUndefinedTypes = assumeAccurateNullUndefinedTypes;
}

/**
Expand Down Expand Up @@ -1071,7 +1074,7 @@ private enum BooleanCoercability {
RIGHT
}

private static BooleanCoercability canConvertComparisonToBooleanCoercion(
private BooleanCoercability canConvertComparisonToBooleanCoercion(
Node left, Node right, Token op) {
// Convert null or undefined check of an object to coercion.
boolean leftIsNull = left.isNull();
Expand All @@ -1084,10 +1087,11 @@ private static BooleanCoercability canConvertComparisonToBooleanCoercion(
boolean leftIsObjectType = isObjectType(left);
boolean rightIsObjectType = isObjectType(right);
if (op == Token.SHEQ || op == Token.SHNE) {
if ((leftIsObjectType && !left.getJSType().isVoidable() && rightIsNull)
|| (leftIsObjectType && !left.getJSType().isNullable() && rightIsUndefined)
|| (rightIsObjectType && !right.getJSType().isVoidable() && leftIsNull)
|| (rightIsObjectType && !right.getJSType().isNullable() && leftIsUndefined)) {
if ((leftIsObjectType && !left.getJSType().isNullable() && rightIsUndefined)
|| (rightIsObjectType && !right.getJSType().isNullable() && leftIsUndefined)
|| (assumeAccurateNullUndefinedTypes
&& ((leftIsObjectType && !left.getJSType().isVoidable() && rightIsNull)
|| (rightIsObjectType && !right.getJSType().isVoidable() && leftIsNull)))) {
return leftIsNullOrUndefined ? BooleanCoercability.RIGHT : BooleanCoercability.LEFT;
}
} else {
Expand Down
Expand Up @@ -46,7 +46,8 @@ public void process(Node externs, Node js) {
new MinimizeExitPoints(compiler).asCompilerPass().process(externs, js);
new PeepholeOptimizationsPass(compiler,
new PeepholeRemoveDeadCode(),
new PeepholeMinimizeConditions(true /* late */, false /* useTypes */),
new PeepholeMinimizeConditions(true /* late */, false /* useTypes */,
false /* assumeAccurateNullUndefinedTypes */),
new PeepholeFoldConstants(true, false))
.process(externs, js);
new MinimizeExitPoints(compiler).asCompilerPass().process(externs, js);
Expand Down
3 changes: 2 additions & 1 deletion test/com/google/javascript/jscomp/MultiPassTest.java
Expand Up @@ -188,7 +188,8 @@ private void addPeephole() {
protected CompilerPass create(AbstractCompiler compiler) {
final boolean late = false;
return new PeepholeOptimizationsPass(compiler,
new PeepholeMinimizeConditions(late, false /* useTypes */),
new PeepholeMinimizeConditions(
late, false /* useTypes */, false /* assumeAccurateNullUndefinedTypes */),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeReplaceKnownMethods(late, false),
new PeepholeRemoveDeadCode(),
Expand Down
Expand Up @@ -33,7 +33,8 @@ public void setUp() throws Exception {
public CompilerPass getProcessor(final Compiler compiler) {
PeepholeOptimizationsPass peepholePass =
new PeepholeOptimizationsPass(compiler,
new PeepholeMinimizeConditions(late, false /* useTypes */),
new PeepholeMinimizeConditions(
late, false /* useTypes */, false /* assumeAccurateNullUndefinedTypes */),
new PeepholeSubstituteAlternateSyntax(late),
new PeepholeRemoveDeadCode(),
new PeepholeFoldConstants(late, false)
Expand Down
Expand Up @@ -25,18 +25,20 @@ public final class PeepholeMinimizeConditionsTest extends CompilerTestCase {

private boolean late = true;
private boolean useTypes = true;
private boolean assumeAccurateNullUndefinedTypes = true;

@Override
public void setUp() throws Exception {
super.setUp();
late = true;
useTypes = true;
assumeAccurateNullUndefinedTypes = true;
}

@Override
public CompilerPass getProcessor(final Compiler compiler) {
PeepholeOptimizationsPass peepholePass = new PeepholeOptimizationsPass(
compiler, new PeepholeMinimizeConditions(late, useTypes));
compiler, new PeepholeMinimizeConditions(late, useTypes, assumeAccurateNullUndefinedTypes));
peepholePass.setRetraverseOnChange(false);
return peepholePass;
}
Expand Down Expand Up @@ -866,6 +868,9 @@ public void testCoercionSubstitution_nullableType() {
"if (/** @type {Array|undefined} */ (window['c']) == null) {}",
"if (!/** @type {Array|undefined} */ (window['c'])) {}");
testSame("if (/** @type {Array|undefined} */ (window['c']) === null) {}");

assumeAccurateNullUndefinedTypes = false;
testSame("var x = /** @type {?Object} */ ({}); if (x !== null) throw 'a';");
}

public void testCoercionSubstitution_unknownType() {
Expand Down

0 comments on commit 14b6188

Please sign in to comment.