8067767: type inference performance regression
authormcimadamore
Thu, 19 Nov 2015 16:43:11 +0000
changeset 33917 45d04023e689
parent 33916 9b8030e8e0d9
child 33918 6d7a40b2a54b
8067767: type inference performance regression Summary: Overhaul implememntation of inference incorporation Reviewed-by: vromero
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Printer.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
langtools/test/tools/javac/diags/examples.not-yet.txt
langtools/test/tools/javac/generics/inference/7154127/T7154127.out
langtools/test/tools/javac/generics/inference/8067767/T8067767.java
langtools/test/tools/javac/lambda/TargetType28.out
langtools/test/tools/javac/lib/DPrinter.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Printer.java	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Printer.java	Thu Nov 19 16:43:11 2015 +0000
@@ -175,8 +175,8 @@
 
     @Override
     public String visitUndetVar(UndetVar t, Locale locale) {
-        if (t.inst != null) {
-            return printAnnotations(t) + visit(t.inst, locale);
+        if (t.getInst() != null) {
+            return printAnnotations(t) + visit(t.getInst(), locale);
         } else {
             return printAnnotations(t) + visit(t.qtype, locale) + "?";
         }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java	Thu Nov 19 16:43:11 2015 +0000
@@ -26,19 +26,20 @@
 package com.sun.tools.javac.code;
 
 import java.lang.annotation.Annotation;
+import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.EnumMap;
-import java.util.EnumSet;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Function;
 
 import javax.lang.model.type.*;
 
 import com.sun.tools.javac.code.Symbol.*;
 import com.sun.tools.javac.code.TypeMetadata.Entry;
+import com.sun.tools.javac.comp.Infer.IncorporationAction;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.DefinedBy.Api;
+
 import static com.sun.tools.javac.code.BoundKind.*;
 import static com.sun.tools.javac.code.Flags.*;
 import static com.sun.tools.javac.code.Kinds.Kind.*;
@@ -1826,17 +1827,15 @@
          */
         public interface UndetVarListener {
             /** called when some inference variable bounds (of given kinds ibs) change */
-            void varChanged(UndetVar uv, Set<InferenceBound> ibs);
+            void varBoundChanged(UndetVar uv, InferenceBound ib, Type bound, boolean update);
+            /** called when the inferred type is set on some inference variable */
+            default void varInstantiated(UndetVar uv) { Assert.error(); }
         }
 
         /**
          * Inference variable bound kinds
          */
         public enum InferenceBound {
-            /** upper bounds */
-            UPPER {
-                public InferenceBound complement() { return LOWER; }
-            },
             /** lower bounds */
             LOWER {
                 public InferenceBound complement() { return UPPER; }
@@ -1844,16 +1843,38 @@
             /** equality constraints */
             EQ {
                 public InferenceBound complement() { return EQ; }
+            },
+            /** upper bounds */
+            UPPER {
+                public InferenceBound complement() { return LOWER; }
             };
 
             public abstract InferenceBound complement();
+
+            public boolean lessThan(InferenceBound that) {
+                if (that == this) {
+                    return false;
+                } else {
+                    switch (that) {
+                        case UPPER: return true;
+                        case LOWER: return false;
+                        case EQ: return (this != UPPER);
+                        default:
+                            Assert.error("Cannot get here!");
+                            return false;
+                    }
+                }
+            }
         }
 
+        /** list of incorporation actions (used by the incorporation engine). */
+        public ArrayDeque<IncorporationAction> incorporationActions = new ArrayDeque<>();
+
         /** inference variable bounds */
         protected Map<InferenceBound, List<Type>> bounds;
 
         /** inference variable's inferred type (set from Infer.java) */
-        public Type inst = null;
+        private Type inst = null;
 
         /** number of declared (upper) bounds */
         public int declaredCount;
@@ -1866,15 +1887,20 @@
             return v.visitUndetVar(this, s);
         }
 
-        public UndetVar(TypeVar origin, Types types) {
+        public UndetVar(TypeVar origin, UndetVarListener listener, Types types) {
             // This is a synthesized internal type, so we cannot annotate it.
             super(UNDETVAR, origin);
+            this.listener = listener;
             bounds = new EnumMap<>(InferenceBound.class);
             List<Type> declaredBounds = types.getBounds(origin);
             declaredCount = declaredBounds.length();
-            bounds.put(InferenceBound.UPPER, declaredBounds);
-            bounds.put(InferenceBound.LOWER, List.<Type>nil());
-            bounds.put(InferenceBound.EQ, List.<Type>nil());
+            bounds.put(InferenceBound.UPPER, List.nil());
+            bounds.put(InferenceBound.LOWER, List.nil());
+            bounds.put(InferenceBound.EQ, List.nil());
+            for (Type t : declaredBounds.reverse()) {
+                //add bound works in reverse order
+                addBound(InferenceBound.UPPER, t, types, true);
+            }
         }
 
         @DefinedBy(Api.LANGUAGE_MODEL)
@@ -1904,6 +1930,32 @@
             return result;
         }
 
+        /**
+         * Returns a new copy of this undet var.
+         */
+        public UndetVar dup(Types types) {
+            UndetVar uv2 = new UndetVar((TypeVar)qtype, listener, types);
+            dupTo(uv2, types);
+            return uv2;
+        }
+
+        /**
+         * Dumps the contents of this undet var on another undet var.
+         */
+        public void dupTo(UndetVar uv2, Types types) {
+            uv2.listener = null;
+            uv2.bounds.clear();
+            for (InferenceBound ib : InferenceBound.values()) {
+                uv2.bounds.put(ib, List.nil());
+                for (Type t : getBounds(ib)) {
+                    uv2.addBound(ib, t, types, true);
+                }
+            }
+            uv2.inst = inst;
+            uv2.listener = listener;
+            uv2.incorporationActions = new ArrayDeque<>(incorporationActions);
+        }
+
         @Override
         public UndetVar cloneWithMetadata(TypeMetadata md) {
             throw new AssertionError("Cannot add metadata to an UndetVar type");
@@ -1919,6 +1971,17 @@
             return (inst == null) ? this : inst.baseType();
         }
 
+        public Type getInst() {
+            return inst;
+        }
+
+        public void setInst(Type inst) {
+            this.inst = inst;
+            if (listener != null) {
+                listener.varInstantiated(this);
+            }
+        }
+
         /** get all bounds of a given kind */
         public List<Type> getBounds(InferenceBound... ibs) {
             ListBuffer<Type> buf = new ListBuffer<>();
@@ -1952,13 +2015,14 @@
         protected void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
             Type bound2 = bound.map(toTypeVarMap).baseType();
             List<Type> prevBounds = bounds.get(ib);
+            if (bound == qtype) return;
             for (Type b : prevBounds) {
                 //check for redundancy - use strict version of isSameType on tvars
                 //(as the standard version will lead to false positives w.r.t. clones ivars)
-                if (types.isSameType(b, bound2, true) || bound == qtype) return;
+                if (types.isSameType(b, bound2, true)) return;
             }
             bounds.put(ib, prevBounds.prepend(bound2));
-            notifyChange(EnumSet.of(ib));
+            notifyBoundChange(ib, bound2, false);
         }
         //where
             TypeMapping<Void> toTypeVarMap = new TypeMapping<Void>() {
@@ -1970,16 +2034,14 @@
 
         /** replace types in all bounds - this might trigger listener notification */
         public void substBounds(List<Type> from, List<Type> to, Types types) {
-            List<Type> instVars = from.diff(to);
-            //if set of instantiated ivars is empty, there's nothing to do!
-            if (instVars.isEmpty()) return;
-            final EnumSet<InferenceBound> boundsChanged = EnumSet.noneOf(InferenceBound.class);
+            final ListBuffer<Pair<InferenceBound, Type>>  boundsChanged = new ListBuffer<>();
             UndetVarListener prevListener = listener;
             try {
                 //setup new listener for keeping track of changed bounds
                 listener = new UndetVarListener() {
-                    public void varChanged(UndetVar uv, Set<InferenceBound> ibs) {
-                        boundsChanged.addAll(ibs);
+                    public void varBoundChanged(UndetVar uv, InferenceBound ib, Type t, boolean _ignored) {
+                        Assert.check(uv == UndetVar.this);
+                        boundsChanged.add(new Pair<>(ib, t));
                     }
                 };
                 for (Map.Entry<InferenceBound, List<Type>> _entry : bounds.entrySet()) {
@@ -1989,7 +2051,7 @@
                     ListBuffer<Type> deps = new ListBuffer<>();
                     //step 1 - re-add bounds that are not dependent on ivars
                     for (Type t : prevBounds) {
-                        if (!t.containsAny(instVars)) {
+                        if (!t.containsAny(from)) {
                             newBounds.append(t);
                         } else {
                             deps.append(t);
@@ -2004,15 +2066,15 @@
                 }
             } finally {
                 listener = prevListener;
-                if (!boundsChanged.isEmpty()) {
-                    notifyChange(boundsChanged);
+                for (Pair<InferenceBound, Type> boundUpdate : boundsChanged) {
+                    notifyBoundChange(boundUpdate.fst, boundUpdate.snd, true);
                 }
             }
         }
 
-        private void notifyChange(EnumSet<InferenceBound> ibs) {
+        private void notifyBoundChange(InferenceBound ib, Type bound, boolean update) {
             if (listener != null) {
-                listener.varChanged(this, ibs);
+                listener.varBoundChanged(this, ib, bound, update);
             }
         }
 
@@ -2029,10 +2091,10 @@
      */
     public static class CapturedUndetVar extends UndetVar {
 
-        public CapturedUndetVar(CapturedType origin, Types types) {
-            super(origin, types);
+        public CapturedUndetVar(CapturedType origin, UndetVarListener listener, Types types) {
+            super(origin, listener, types);
             if (!origin.lower.hasTag(BOT)) {
-                bounds.put(InferenceBound.LOWER, List.of(origin.lower));
+                addBound(InferenceBound.LOWER, origin.lower, types, true);
             }
         }
 
@@ -2051,6 +2113,12 @@
         public boolean isCaptured() {
             return true;
         }
+
+        public UndetVar dup(Types types) {
+            UndetVar uv2 = new CapturedUndetVar((CapturedType)qtype, listener, types);
+            dupTo(uv2, types);
+            return uv2;
+        }
     }
 
     /** Represents NONE.
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java	Thu Nov 19 16:43:11 2015 +0000
@@ -3843,7 +3843,7 @@
 
             @Override
             public Integer visitTypeVar(TypeVar t, Void ignored) {
-                return System.identityHashCode(t.tsym);
+                return System.identityHashCode(t);
             }
 
             @Override
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java	Thu Nov 19 16:43:11 2015 +0000
@@ -25,6 +25,7 @@
 
 package com.sun.tools.javac.comp;
 
+import com.sun.tools.javac.code.Type.UndetVar.UndetVarListener;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.JCTree.JCTypeCast;
 import com.sun.tools.javac.tree.TreeInfo;
@@ -57,6 +58,8 @@
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 
 import static com.sun.tools.javac.code.TypeTag.*;
 
@@ -182,14 +185,14 @@
                     argtypes, mt.getParameterTypes(), warn);
 
             if (allowGraphInference && resultInfo != null && resultInfo.pt == anyPoly) {
-                checkWithinBounds(inferenceContext, warn);
+                doIncorporation(inferenceContext, warn);
                 //we are inside method attribution - just return a partially inferred type
                 return new PartiallyInferredMethodType(mt, inferenceContext, env, warn);
             } else if (allowGraphInference &&
                     resultInfo != null &&
                     !warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) {
                 //inject return constraints earlier
-                checkWithinBounds(inferenceContext, warn); //propagation
+                doIncorporation(inferenceContext, warn); //propagation
 
                 boolean shouldPropagate = resultInfo.checkContext.inferenceContext().free(resultInfo.pt);
 
@@ -501,7 +504,7 @@
         inferenceContext.solve(List.of(from.qtype), new Warner());
         inferenceContext.notifyChange();
         Type capturedType = resultInfo.checkContext.inferenceContext()
-                .cachedCapture(tree, from.inst, false);
+                .cachedCapture(tree, from.getInst(), false);
         if (types.isConvertible(capturedType,
                 resultInfo.checkContext.inferenceContext().asUndetVar(to))) {
             //effectively skip additional return-type constraint generation (compatibility)
@@ -523,22 +526,22 @@
                 TypeSymbol fresh_tvar = new TypeVariableSymbol(Flags.SYNTHETIC, uv.qtype.tsym.name, null, uv.qtype.tsym.owner);
                 fresh_tvar.type = new TypeVar(fresh_tvar, types.makeIntersectionType(uv.getBounds(InferenceBound.UPPER)), null);
                 todo.append(uv);
-                uv.inst = fresh_tvar.type;
+                uv.setInst(fresh_tvar.type);
             } else if (upperBounds.nonEmpty()) {
-                uv.inst = types.glb(upperBounds);
+                uv.setInst(types.glb(upperBounds));
             } else {
-                uv.inst = syms.objectType;
+                uv.setInst(syms.objectType);
             }
         }
         //step 2 - replace fresh tvars in their bounds
         List<Type> formals = vars;
         for (Type t : todo) {
             UndetVar uv = (UndetVar)t;
-            TypeVar ct = (TypeVar)uv.inst;
+            TypeVar ct = (TypeVar)uv.getInst();
             ct.bound = types.glb(inferenceContext.asInstTypes(types.getBounds(ct)));
             if (ct.bound.isErroneous()) {
                 //report inference error if glb fails
-                reportBoundError(uv, BoundErrorKind.BAD_UPPER);
+                reportBoundError(uv, InferenceBound.UPPER);
             }
             formals = formals.tail;
         }
@@ -619,12 +622,12 @@
     TypeMapping<Void> fromTypeVarFun = new TypeMapping<Void>() {
         @Override
         public Type visitTypeVar(TypeVar tv, Void aVoid) {
-            return new UndetVar(tv, types);
+            return new UndetVar(tv, incorporationEngine(), types);
         }
 
         @Override
         public Type visitCapturedType(CapturedType t, Void aVoid) {
-            return new CapturedUndetVar(t, types);
+            return new CapturedUndetVar(t, incorporationEngine(), types);
         }
     };
 
@@ -671,8 +674,8 @@
             List<Type> actualTypeargs = funcInterface.getTypeArguments();
             for (Type t : funcInterfaceContext.undetvars) {
                 UndetVar uv = (UndetVar)t;
-                if (uv.inst == null) {
-                    uv.inst = actualTypeargs.head;
+                if (uv.getInst() == null) {
+                    uv.setInst(actualTypeargs.head);
                 }
                 actualTypeargs = actualTypeargs.tail;
             }
@@ -690,88 +693,399 @@
     }
     // </editor-fold>
 
-    // <editor-fold defaultstate="collapsed" desc="Bound checking">
+    // <editor-fold defaultstate="collapsed" desc="Incorporation">
+
     /**
-     * Check bounds and perform incorporation
+     * This class is the root of all incorporation actions.
      */
-    void checkWithinBounds(InferenceContext inferenceContext,
-                             Warner warn) throws InferenceException {
-        MultiUndetVarListener mlistener = new MultiUndetVarListener(inferenceContext.undetvars);
-        List<Type> saved_undet = inferenceContext.save();
-        try {
-            while (true) {
-                mlistener.reset();
-                if (!allowGraphInference) {
-                    //in legacy mode we lack of transitivity, so bound check
-                    //cannot be run in parallel with other incoprporation rounds
-                    for (Type t : inferenceContext.undetvars) {
-                        UndetVar uv = (UndetVar)t;
-                        IncorporationStep.CHECK_BOUNDS.apply(uv, inferenceContext, warn);
-                    }
-                }
-                for (Type t : inferenceContext.undetvars) {
-                    UndetVar uv = (UndetVar)t;
-                    //bound incorporation
-                    EnumSet<IncorporationStep> incorporationSteps = allowGraphInference ?
-                            incorporationStepsGraph : incorporationStepsLegacy;
-                    for (IncorporationStep is : incorporationSteps) {
-                        if (is.accepts(uv, inferenceContext)) {
-                            is.apply(uv, inferenceContext, warn);
-                        }
-                    }
-                }
-                if (!mlistener.changed || !allowGraphInference) break;
-            }
+    public abstract class IncorporationAction {
+        UndetVar uv;
+        Type t;
+
+        IncorporationAction(UndetVar uv, Type t) {
+            this.uv = uv;
+            this.t = t;
         }
-        finally {
-            mlistener.detach();
-            if (incorporationCache.size() == MAX_INCORPORATION_STEPS) {
-                inferenceContext.rollback(saved_undet);
-            }
-            incorporationCache.clear();
+
+        /**
+         * Incorporation action entry-point. Subclasses should define the logic associated with
+         * this incorporation action.
+         */
+        abstract void apply(InferenceContext ic, Warner warn);
+
+        /**
+         * Helper function: perform subtyping through incorporation cache.
+         */
+        boolean isSubtype(Type s, Type t, Warner warn) {
+            return doIncorporationOp(IncorporationBinaryOpKind.IS_SUBTYPE, s, t, warn);
+        }
+
+        /**
+         * Helper function: perform type-equivalence through incorporation cache.
+         */
+        boolean isSameType(Type s, Type t) {
+            return doIncorporationOp(IncorporationBinaryOpKind.IS_SAME_TYPE, s, t, null);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s[undet=%s,t=%s]", getClass().getSimpleName(), uv.qtype, t);
         }
     }
-    //where
-        /**
-         * This listener keeps track of changes on a group of inference variable
-         * bounds. Note: the listener must be detached (calling corresponding
-         * method) to make sure that the underlying inference variable is
-         * left in a clean state.
-         */
-        class MultiUndetVarListener implements UndetVar.UndetVarListener {
+
+    /**
+     * Bound-check incorporation action. A newly added bound is checked against existing bounds,
+     * to verify its compatibility; each bound is checked using either subtyping or type equivalence.
+     */
+    class CheckBounds extends IncorporationAction {
 
-            boolean changed;
-            List<Type> undetvars;
+        InferenceBound from;
+        BiFunction<InferenceContext, Type, Type> typeFunc;
+        BiPredicate<InferenceContext, Type> optFilter;
+
+        CheckBounds(UndetVar uv, Type t, InferenceBound from) {
+            this(uv, t, InferenceContext::asUndetVar, null, from);
+        }
 
-            public MultiUndetVarListener(List<Type> undetvars) {
-                this.undetvars = undetvars;
-                for (Type t : undetvars) {
-                    UndetVar uv = (UndetVar)t;
-                    uv.listener = this;
-                }
-            }
+        CheckBounds(UndetVar uv, Type t, BiFunction<InferenceContext, Type, Type> typeFunc,
+                    BiPredicate<InferenceContext, Type> typeFilter, InferenceBound from) {
+            super(uv, t);
+            this.from = from;
+            this.typeFunc = typeFunc;
+            this.optFilter = typeFilter;
+        }
 
-            public void varChanged(UndetVar uv, Set<InferenceBound> ibs) {
-                //avoid non-termination
-                if (incorporationCache.size() < MAX_INCORPORATION_STEPS) {
-                    changed = true;
-                }
-            }
-
-            void reset() {
-                changed = false;
-            }
-
-            void detach() {
-                for (Type t : undetvars) {
-                    UndetVar uv = (UndetVar)t;
-                    uv.listener = null;
+        @Override
+        void apply(InferenceContext inferenceContext, Warner warn) {
+            t = typeFunc.apply(inferenceContext, t);
+            if (optFilter != null && optFilter.test(inferenceContext, t)) return;
+            for (InferenceBound to : boundsToCheck()) {
+                for (Type b : uv.getBounds(to)) {
+                    b = typeFunc.apply(inferenceContext, b);
+                    if (optFilter != null && optFilter.test(inferenceContext, b)) continue;
+                    boolean success = checkBound(t, b, from, to, warn);
+                    if (!success) {
+                        report(from, to);
+                    }
                 }
             }
         }
 
-    /** max number of incorporation rounds */
-        static final int MAX_INCORPORATION_STEPS = 100;
+        /**
+         * The list of bound kinds to be checked.
+         */
+        EnumSet<InferenceBound> boundsToCheck() {
+            return (from == InferenceBound.EQ) ?
+                            EnumSet.allOf(InferenceBound.class) :
+                            EnumSet.complementOf(EnumSet.of(from));
+        }
+
+        /**
+         * Is source type 's' compatible with target type 't' given source and target bound kinds?
+         */
+        boolean checkBound(Type s, Type t, InferenceBound ib_s, InferenceBound ib_t, Warner warn) {
+            if (ib_s.lessThan(ib_t)) {
+                return isSubtype(s, t, warn);
+            } else if (ib_t.lessThan(ib_s)) {
+                return isSubtype(t, s, warn);
+            } else {
+                return isSameType(s, t);
+            }
+        }
+
+        /**
+         * Report a bound check error.
+         */
+        void report(InferenceBound from, InferenceBound to) {
+            //this is a workaround to preserve compatibility with existing messages
+            if (from == to) {
+                reportBoundError(uv, from);
+            } else if (from == InferenceBound.LOWER || to == InferenceBound.EQ) {
+                reportBoundError(uv, to, from);
+            } else {
+                reportBoundError(uv, from, to);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s[undet=%s,t=%s,bound=%s]", getClass().getSimpleName(), uv.qtype, t, from);
+        }
+    }
+
+    /**
+     * Custom check executed by the legacy incorporation engine. Newly added bounds are checked
+     * against existing eq bounds.
+     */
+    class EqCheckLegacy extends CheckBounds {
+        EqCheckLegacy(UndetVar uv, Type t, InferenceBound from) {
+            super(uv, t, InferenceContext::asInstType, InferenceContext::free, from);
+        }
+
+        @Override
+        EnumSet<InferenceBound> boundsToCheck() {
+            return (from == InferenceBound.EQ) ?
+                            EnumSet.allOf(InferenceBound.class) :
+                            EnumSet.of(InferenceBound.EQ);
+        }
+    }
+
+    /**
+     * Check that the inferred type conforms to all bounds.
+     */
+    class CheckInst extends CheckBounds {
+
+        EnumSet<InferenceBound> to;
+
+        CheckInst(UndetVar uv, InferenceBound ib, InferenceBound... rest) {
+            super(uv, uv.getInst(), InferenceBound.EQ);
+            this.to = EnumSet.of(ib, rest);
+        }
+
+        @Override
+        EnumSet<InferenceBound> boundsToCheck() {
+            return to;
+        }
+
+        @Override
+        void report(InferenceBound from, InferenceBound to) {
+            reportInstError(uv, to);
+        }
+    }
+
+    /**
+     * Replace undetvars in bounds and check that the inferred type conforms to all bounds.
+     */
+    class SubstBounds extends CheckInst {
+        SubstBounds(UndetVar uv) {
+            super(uv, InferenceBound.LOWER, InferenceBound.EQ, InferenceBound.UPPER);
+        }
+
+        @Override
+        void apply(InferenceContext inferenceContext, Warner warn) {
+            for (Type undet : inferenceContext.undetvars) {
+                //we could filter out variables not mentioning uv2...
+                UndetVar uv2 = (UndetVar)undet;
+                uv2.substBounds(List.of(uv.qtype), List.of(uv.getInst()), types);
+                checkCompatibleUpperBounds(uv2, inferenceContext);
+            }
+            super.apply(inferenceContext, warn);
+        }
+
+        /**
+         * Make sure that the upper bounds we got so far lead to a solvable inference
+         * variable by making sure that a glb exists.
+         */
+        void checkCompatibleUpperBounds(UndetVar uv, InferenceContext inferenceContext) {
+            List<Type> hibounds =
+                    Type.filter(uv.getBounds(InferenceBound.UPPER), new BoundFilter(inferenceContext));
+            final Type hb;
+            if (hibounds.isEmpty())
+                hb = syms.objectType;
+            else if (hibounds.tail.isEmpty())
+                hb = hibounds.head;
+            else
+                hb = types.glb(hibounds);
+            if (hb == null || hb.isErroneous())
+                reportBoundError(uv, InferenceBound.UPPER);
+        }
+    }
+
+    /**
+     * Perform pairwise comparison between common generic supertypes of two upper bounds.
+     */
+    class CheckUpperBounds extends IncorporationAction {
+
+        public CheckUpperBounds(UndetVar uv, Type t) {
+            super(uv, t);
+        }
+
+        @Override
+        void apply(InferenceContext inferenceContext, Warner warn) {
+            List<Type> boundList = uv.getBounds(InferenceBound.UPPER).stream()
+                    .collect(types.closureCollector(true, types::isSameType));
+            for (Type b2 : boundList) {
+                if (t == b2) continue;
+                    /* This wildcard check is temporary workaround. This code may need to be
+                     * revisited once spec bug JDK-7034922 is fixed.
+                     */
+                if (t != b2 && !t.hasTag(WILDCARD) && !b2.hasTag(WILDCARD)) {
+                    for (Pair<Type, Type> commonSupers : getParameterizedSupers(t, b2)) {
+                        List<Type> allParamsSuperBound1 = commonSupers.fst.allparams();
+                        List<Type> allParamsSuperBound2 = commonSupers.snd.allparams();
+                        while (allParamsSuperBound1.nonEmpty() && allParamsSuperBound2.nonEmpty()) {
+                            //traverse the list of all params comparing them
+                            if (!allParamsSuperBound1.head.hasTag(WILDCARD) &&
+                                    !allParamsSuperBound2.head.hasTag(WILDCARD)) {
+                                if (!isSameType(inferenceContext.asUndetVar(allParamsSuperBound1.head),
+                                        inferenceContext.asUndetVar(allParamsSuperBound2.head))) {
+                                    reportBoundError(uv, InferenceBound.UPPER);
+                                }
+                            }
+                            allParamsSuperBound1 = allParamsSuperBound1.tail;
+                            allParamsSuperBound2 = allParamsSuperBound2.tail;
+                        }
+                        Assert.check(allParamsSuperBound1.isEmpty() && allParamsSuperBound2.isEmpty());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Perform propagation of bounds. Given a constraint of the kind {@code alpha <: T}, three
+     * kind of propagation occur:
+     *
+     * <li>T is copied into all matching bounds (i.e. lower/eq bounds) B of alpha such that B=beta (forward propagation)</li>
+     * <li>if T=beta, matching bounds (i.e. upper bounds) of beta are copied into alpha (backwards propagation)</li>
+     * <li>if T=beta, sets a symmetric bound on beta (i.e. beta :> alpha) (symmetric propagation) </li>
+     */
+    class PropagateBounds extends IncorporationAction {
+
+        InferenceBound ib;
+
+        public PropagateBounds(UndetVar uv, Type t, InferenceBound ib) {
+            super(uv, t);
+            this.ib = ib;
+        }
+
+        void apply(InferenceContext inferenceContext, Warner warner) {
+            Type undetT = inferenceContext.asUndetVar(t);
+            if (undetT.hasTag(UNDETVAR) && !((UndetVar)undetT).isCaptured()) {
+                UndetVar uv2 = (UndetVar)undetT;
+                //symmetric propagation
+                uv2.addBound(ib.complement(), uv, types);
+                //backwards propagation
+                for (InferenceBound ib2 : backwards()) {
+                    for (Type b : uv2.getBounds(ib2)) {
+                        uv.addBound(ib2, b, types);
+                    }
+                }
+            }
+            //forward propagation
+            for (InferenceBound ib2 : forward()) {
+                for (Type l : uv.getBounds(ib2)) {
+                    Type undet = inferenceContext.asUndetVar(l);
+                    if (undet.hasTag(TypeTag.UNDETVAR) && !((UndetVar)undet).isCaptured()) {
+                        UndetVar uv2 = (UndetVar)undet;
+                        uv2.addBound(ib, inferenceContext.asInstType(t), types);
+                    }
+                }
+            }
+        }
+
+        EnumSet<InferenceBound> forward() {
+            return (ib == InferenceBound.EQ) ?
+                    EnumSet.of(InferenceBound.EQ) : EnumSet.complementOf(EnumSet.of(ib));
+        }
+
+        EnumSet<InferenceBound> backwards() {
+            return (ib == InferenceBound.EQ) ?
+                    EnumSet.allOf(InferenceBound.class) : EnumSet.of(ib);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s[undet=%s,t=%s,bound=%s]", getClass().getSimpleName(), uv.qtype, t, ib);
+        }
+    }
+
+    /**
+     * This class models an incorporation engine. The engine is responsible for listening to
+     * changes in inference variables and register incorporation actions accordingly.
+     */
+    abstract class AbstractIncorporationEngine implements UndetVarListener {
+
+        @Override
+        public void varInstantiated(UndetVar uv) {
+            uv.incorporationActions.addFirst(new SubstBounds(uv));
+        }
+
+        @Override
+        public void varBoundChanged(UndetVar uv, InferenceBound ib, Type bound, boolean update) {
+            if (uv.isCaptured()) return;
+            uv.incorporationActions.addAll(getIncorporationActions(uv, ib, bound, update));
+        }
+
+        abstract List<IncorporationAction> getIncorporationActions(UndetVar uv, InferenceBound ib, Type t, boolean update);
+    }
+
+    /**
+     * A legacy incorporation engine. Used for source <= 7.
+     */
+    AbstractIncorporationEngine legacyEngine = new AbstractIncorporationEngine() {
+
+        List<IncorporationAction> getIncorporationActions(UndetVar uv, InferenceBound ib, Type t, boolean update) {
+            ListBuffer<IncorporationAction> actions = new ListBuffer<>();
+            Type inst = uv.getInst();
+            if (inst != null) {
+                actions.add(new CheckInst(uv, ib));
+            }
+            actions.add(new EqCheckLegacy(uv, t, ib));
+            return actions.toList();
+        }
+    };
+
+    /**
+     * The standard incorporation engine. Used for source >= 8.
+     */
+    AbstractIncorporationEngine graphEngine = new AbstractIncorporationEngine() {
+
+        @Override
+        List<IncorporationAction> getIncorporationActions(UndetVar uv, InferenceBound ib, Type t, boolean update) {
+            ListBuffer<IncorporationAction> actions = new ListBuffer<>();
+            Type inst = uv.getInst();
+            if (inst != null) {
+                actions.add(new CheckInst(uv, ib));
+            }
+            actions.add(new CheckBounds(uv, t, ib));
+
+            if (update) {
+                return actions.toList();
+            }
+
+            if (ib == InferenceBound.UPPER) {
+                actions.add(new CheckUpperBounds(uv, t));
+            }
+
+            actions.add(new PropagateBounds(uv, t, ib));
+
+            return actions.toList();
+        }
+    };
+
+    /**
+     * Get the incorporation engine to be used in this compilation.
+     */
+    AbstractIncorporationEngine incorporationEngine() {
+        return allowGraphInference ? graphEngine : legacyEngine;
+    }
+
+    /** max number of incorporation rounds. */
+    static final int MAX_INCORPORATION_STEPS = 10000;
+
+    /**
+     * Check bounds and perform incorporation.
+     */
+    void doIncorporation(InferenceContext inferenceContext, Warner warn) throws InferenceException {
+        try {
+            boolean progress = true;
+            int round = 0;
+            while (progress && round < MAX_INCORPORATION_STEPS) {
+                progress = false;
+                for (Type t : inferenceContext.undetvars) {
+                    UndetVar uv = (UndetVar)t;
+                    if (!uv.incorporationActions.isEmpty()) {
+                        progress = true;
+                        uv.incorporationActions.removeFirst().apply(inferenceContext, warn);
+                    }
+                }
+                round++;
+            }
+        } finally {
+            incorporationCache.clear();
+        }
+    }
 
     /* If for two types t and s there is a least upper bound that contains
      * parameterized types G1, G2 ... Gn, then there exists supertypes of 't' of the form
@@ -813,407 +1127,14 @@
                     types.asSuper(t, sup.tsym);
         }
 
-    /**
-     * This enumeration defines an entry point for doing inference variable
-     * bound incorporation - it can be used to inject custom incorporation
-     * logic into the basic bound checking routine
-     */
-    enum IncorporationStep {
-        /**
-         * Performs basic bound checking - i.e. is the instantiated type for a given
-         * inference variable compatible with its bounds?
-         */
-        CHECK_BOUNDS() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                uv.substBounds(inferenceContext.inferenceVars(), inferenceContext.instTypes(), infer.types);
-                infer.checkCompatibleUpperBounds(uv, inferenceContext);
-                if (uv.inst != null) {
-                    Type inst = uv.inst;
-                    for (Type u : uv.getBounds(InferenceBound.UPPER)) {
-                        if (!isSubtype(inst, inferenceContext.asUndetVar(u), warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.UPPER);
-                        }
-                    }
-                    for (Type l : uv.getBounds(InferenceBound.LOWER)) {
-                        if (!isSubtype(inferenceContext.asUndetVar(l), inst, warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.LOWER);
-                        }
-                    }
-                    for (Type e : uv.getBounds(InferenceBound.EQ)) {
-                        if (!isSameType(inst, inferenceContext.asUndetVar(e), infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.EQ);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                //applies to all undetvars
-                return true;
-            }
-        },
-        /**
-         * Check consistency of equality constraints. This is a slightly more aggressive
-         * inference routine that is designed as to maximize compatibility with JDK 7.
-         * Note: this is not used in graph mode.
-         */
-        EQ_CHECK_LEGACY() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                Type eq = null;
-                for (Type e : uv.getBounds(InferenceBound.EQ)) {
-                    Assert.check(!inferenceContext.free(e));
-                    if (eq != null && !isSameType(e, eq, infer)) {
-                        infer.reportBoundError(uv, BoundErrorKind.EQ);
-                    }
-                    eq = e;
-                    for (Type l : uv.getBounds(InferenceBound.LOWER)) {
-                        Assert.check(!inferenceContext.free(l));
-                        if (!isSubtype(l, e, warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_EQ_LOWER);
-                        }
-                    }
-                    for (Type u : uv.getBounds(InferenceBound.UPPER)) {
-                        if (inferenceContext.free(u)) continue;
-                        if (!isSubtype(e, u, warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_EQ_UPPER);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() && uv.getBounds(InferenceBound.EQ).nonEmpty();
-            }
-        },
-        /**
-         * Check consistency of equality constraints.
-         */
-        EQ_CHECK() {
-            @Override
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type e : uv.getBounds(InferenceBound.EQ)) {
-                    if (e.containsAny(inferenceContext.inferenceVars())) continue;
-                    for (Type u : uv.getBounds(InferenceBound.UPPER)) {
-                        if (!isSubtype(e, inferenceContext.asUndetVar(u), warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_EQ_UPPER);
-                        }
-                    }
-                    for (Type l : uv.getBounds(InferenceBound.LOWER)) {
-                        if (!isSubtype(inferenceContext.asUndetVar(l), e, warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_EQ_LOWER);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() && uv.getBounds(InferenceBound.EQ).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha <: T} and {@code alpha :> S}
-         * perform {@code S <: T} (which could lead to new bounds).
-         */
-        CROSS_UPPER_LOWER() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b1 : uv.getBounds(InferenceBound.UPPER)) {
-                    for (Type b2 : uv.getBounds(InferenceBound.LOWER)) {
-                        if (!isSubtype(inferenceContext.asUndetVar(b2), inferenceContext.asUndetVar(b1), warn , infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_UPPER_LOWER);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.UPPER).nonEmpty() &&
-                        uv.getBounds(InferenceBound.LOWER).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha <: T} and {@code alpha == S}
-         * perform {@code S <: T} (which could lead to new bounds).
-         */
-        CROSS_UPPER_EQ() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b1 : uv.getBounds(InferenceBound.UPPER)) {
-                    for (Type b2 : uv.getBounds(InferenceBound.EQ)) {
-                        if (!isSubtype(inferenceContext.asUndetVar(b2), inferenceContext.asUndetVar(b1), warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_UPPER_EQUAL);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.EQ).nonEmpty() &&
-                        uv.getBounds(InferenceBound.UPPER).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha :> S} and {@code alpha == T}
-         * perform {@code S <: T} (which could lead to new bounds).
-         */
-        CROSS_EQ_LOWER() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b1 : uv.getBounds(InferenceBound.EQ)) {
-                    for (Type b2 : uv.getBounds(InferenceBound.LOWER)) {
-                        if (!isSubtype(inferenceContext.asUndetVar(b2), inferenceContext.asUndetVar(b1), warn, infer)) {
-                            infer.reportBoundError(uv, BoundErrorKind.BAD_EQUAL_LOWER);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.EQ).nonEmpty() &&
-                        uv.getBounds(InferenceBound.LOWER).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha <: P<T>} and
-         * {@code alpha <: P<S>} where P is a parameterized type,
-         * perform {@code T = S} (which could lead to new bounds).
-         */
-        CROSS_UPPER_UPPER() {
-            @Override
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                List<Type> boundList = uv.getBounds(InferenceBound.UPPER).stream()
-                        .collect(infer.types.closureCollector(true, infer.types::isSameType));
-                List<Type> boundListTail = boundList.tail;
-                while (boundList.nonEmpty()) {
-                    List<Type> tmpTail = boundListTail;
-                    while (tmpTail.nonEmpty()) {
-                        Type b1 = boundList.head;
-                        Type b2 = tmpTail.head;
-                        /* This wildcard check is temporary workaround. This code may need to be
-                         * revisited once spec bug JDK-7034922 is fixed.
-                         */
-                        if (b1 != b2 && !b1.hasTag(WILDCARD) && !b2.hasTag(WILDCARD)) {
-                            for (Pair<Type, Type> commonSupers : infer.getParameterizedSupers(b1, b2)) {
-                                List<Type> allParamsSuperBound1 = commonSupers.fst.allparams();
-                                List<Type> allParamsSuperBound2 = commonSupers.snd.allparams();
-                                while (allParamsSuperBound1.nonEmpty() && allParamsSuperBound2.nonEmpty()) {
-                                    //traverse the list of all params comparing them
-                                    if (!allParamsSuperBound1.head.hasTag(WILDCARD) &&
-                                        !allParamsSuperBound2.head.hasTag(WILDCARD)) {
-                                        if (!isSameType(inferenceContext.asUndetVar(allParamsSuperBound1.head),
-                                            inferenceContext.asUndetVar(allParamsSuperBound2.head), infer)) {
-                                            infer.reportBoundError(uv, BoundErrorKind.BAD_UPPER);
-                                        }
-                                    }
-                                    allParamsSuperBound1 = allParamsSuperBound1.tail;
-                                    allParamsSuperBound2 = allParamsSuperBound2.tail;
-                                }
-                                Assert.check(allParamsSuperBound1.isEmpty() && allParamsSuperBound2.isEmpty());
-                            }
-                        }
-                        tmpTail = tmpTail.tail;
-                    }
-                    boundList = boundList.tail;
-                    boundListTail = boundList.tail;
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.UPPER).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha == S} and {@code alpha == T}
-         * perform {@code S == T} (which could lead to new bounds).
-         */
-        CROSS_EQ_EQ() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b1 : uv.getBounds(InferenceBound.EQ)) {
-                    for (Type b2 : uv.getBounds(InferenceBound.EQ)) {
-                        if (b1 != b2) {
-                            if (!isSameType(inferenceContext.asUndetVar(b2), inferenceContext.asUndetVar(b1), infer)) {
-                                infer.reportBoundError(uv, BoundErrorKind.BAD_EQ);
-                            }
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.EQ).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha <: beta} propagate lower bounds
-         * from alpha to beta; also propagate upper bounds from beta to alpha.
-         */
-        PROP_UPPER() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b : uv.getBounds(InferenceBound.UPPER)) {
-                    if (inferenceContext.inferenceVars().contains(b)) {
-                        UndetVar uv2 = (UndetVar)inferenceContext.asUndetVar(b);
-                        if (uv2.isCaptured()) continue;
-                        //alpha <: beta
-                        //0. set beta :> alpha
-                        addBound(InferenceBound.LOWER, uv2, inferenceContext.asInstType(uv.qtype), infer);
-                        //1. copy alpha's lower to beta's
-                        for (Type l : uv.getBounds(InferenceBound.LOWER)) {
-                            addBound(InferenceBound.LOWER, uv2, inferenceContext.asInstType(l), infer);
-                        }
-                        //2. copy beta's upper to alpha's
-                        for (Type u : uv2.getBounds(InferenceBound.UPPER)) {
-                            addBound(InferenceBound.UPPER, uv, inferenceContext.asInstType(u), infer);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.UPPER).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha :> beta} propagate lower bounds
-         * from beta to alpha; also propagate upper bounds from alpha to beta.
-         */
-        PROP_LOWER() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b : uv.getBounds(InferenceBound.LOWER)) {
-                    if (inferenceContext.inferenceVars().contains(b)) {
-                        UndetVar uv2 = (UndetVar)inferenceContext.asUndetVar(b);
-                        if (uv2.isCaptured()) continue;
-                        //alpha :> beta
-                        //0. set beta <: alpha
-                        addBound(InferenceBound.UPPER, uv2, inferenceContext.asInstType(uv.qtype), infer);
-                        //1. copy alpha's upper to beta's
-                        for (Type u : uv.getBounds(InferenceBound.UPPER)) {
-                            addBound(InferenceBound.UPPER, uv2, inferenceContext.asInstType(u), infer);
-                        }
-                        //2. copy beta's lower to alpha's
-                        for (Type l : uv2.getBounds(InferenceBound.LOWER)) {
-                            addBound(InferenceBound.LOWER, uv, inferenceContext.asInstType(l), infer);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.LOWER).nonEmpty();
-            }
-        },
-        /**
-         * Given a bound set containing {@code alpha == beta} propagate lower/upper
-         * bounds from alpha to beta and back.
-         */
-        PROP_EQ() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
-                Infer infer = inferenceContext.infer;
-                for (Type b : uv.getBounds(InferenceBound.EQ)) {
-                    if (inferenceContext.inferenceVars().contains(b)) {
-                        UndetVar uv2 = (UndetVar)inferenceContext.asUndetVar(b);
-                        if (uv2.isCaptured()) continue;
-                        //alpha == beta
-                        //0. set beta == alpha
-                        addBound(InferenceBound.EQ, uv2, inferenceContext.asInstType(uv.qtype), infer);
-                        //1. copy all alpha's bounds to beta's
-                        for (InferenceBound ib : InferenceBound.values()) {
-                            for (Type b2 : uv.getBounds(ib)) {
-                                if (b2 != uv2) {
-                                    addBound(ib, uv2, inferenceContext.asInstType(b2), infer);
-                                }
-                            }
-                        }
-                        //2. copy all beta's bounds to alpha's
-                        for (InferenceBound ib : InferenceBound.values()) {
-                            for (Type b2 : uv2.getBounds(ib)) {
-                                if (b2 != uv) {
-                                    addBound(ib, uv, inferenceContext.asInstType(b2), infer);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            @Override
-            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-                return !uv.isCaptured() &&
-                        uv.getBounds(InferenceBound.EQ).nonEmpty();
-            }
-        };
-
-        abstract void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn);
-
-        boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
-            return !uv.isCaptured();
-        }
-
-        boolean isSubtype(Type s, Type t, Warner warn, Infer infer) {
-            return doIncorporationOp(IncorporationBinaryOpKind.IS_SUBTYPE, s, t, warn, infer);
-        }
-
-        boolean isSameType(Type s, Type t, Infer infer) {
-            return doIncorporationOp(IncorporationBinaryOpKind.IS_SAME_TYPE, s, t, null, infer);
-        }
-
-        void addBound(InferenceBound ib, UndetVar uv, Type b, Infer infer) {
-            doIncorporationOp(opFor(ib), uv, b, null, infer);
-        }
-
-        IncorporationBinaryOpKind opFor(InferenceBound boundKind) {
-            switch (boundKind) {
-                case EQ:
-                    return IncorporationBinaryOpKind.ADD_EQ_BOUND;
-                case LOWER:
-                    return IncorporationBinaryOpKind.ADD_LOWER_BOUND;
-                case UPPER:
-                    return IncorporationBinaryOpKind.ADD_UPPER_BOUND;
-                default:
-                    Assert.error("Can't get here!");
-                    return null;
-            }
-        }
-
-        boolean doIncorporationOp(IncorporationBinaryOpKind opKind, Type op1, Type op2, Warner warn, Infer infer) {
-            IncorporationBinaryOp newOp = infer.new IncorporationBinaryOp(opKind, op1, op2);
-            Boolean res = infer.incorporationCache.get(newOp);
+    boolean doIncorporationOp(IncorporationBinaryOpKind opKind, Type op1, Type op2, Warner warn) {
+            IncorporationBinaryOp newOp = new IncorporationBinaryOp(opKind, op1, op2);
+            Boolean res = incorporationCache.get(newOp);
             if (res == null) {
-                infer.incorporationCache.put(newOp, res = newOp.apply(warn));
+                incorporationCache.put(newOp, res = newOp.apply(warn));
             }
             return res;
         }
-    }
-
-    /** incorporation steps to be executed when running in legacy mode */
-    EnumSet<IncorporationStep> incorporationStepsLegacy = EnumSet.of(IncorporationStep.EQ_CHECK_LEGACY);
-
-    /** incorporation steps to be executed when running in graph mode */
-    EnumSet<IncorporationStep> incorporationStepsGraph =
-            EnumSet.complementOf(EnumSet.of(IncorporationStep.EQ_CHECK_LEGACY));
 
     /**
      * Three kinds of basic operation are supported as part of an incorporation step:
@@ -1232,30 +1153,6 @@
             boolean apply(Type op1, Type op2, Warner warn, Types types) {
                 return types.isSameType(op1, op2);
             }
-        },
-        ADD_UPPER_BOUND() {
-            @Override
-            boolean apply(Type op1, Type op2, Warner warn, Types types) {
-                UndetVar uv = (UndetVar)op1;
-                uv.addBound(InferenceBound.UPPER, op2, types);
-                return true;
-            }
-        },
-        ADD_LOWER_BOUND() {
-            @Override
-            boolean apply(Type op1, Type op2, Warner warn, Types types) {
-                UndetVar uv = (UndetVar)op1;
-                uv.addBound(InferenceBound.LOWER, op2, types);
-                return true;
-            }
-        },
-        ADD_EQ_BOUND() {
-            @Override
-            boolean apply(Type op1, Type op2, Warner warn, Types types) {
-                UndetVar uv = (UndetVar)op1;
-                uv.addBound(InferenceBound.EQ, op2, types);
-                return true;
-            }
         };
 
         abstract boolean apply(Type op1, Type op2, Warner warn, Types types);
@@ -1296,9 +1193,9 @@
         public int hashCode() {
             int result = opKind.hashCode();
             result *= 127;
-            result += types.hashCode(op1, true);
+            result += types.hashCode(op1);
             result *= 127;
-            result += types.hashCode(op2, true);
+            result += types.hashCode(op2);
             return result;
         }
 
@@ -1310,152 +1207,59 @@
     /** an incorporation cache keeps track of all executed incorporation-related operations */
     Map<IncorporationBinaryOp, Boolean> incorporationCache = new HashMap<>();
 
-    /**
-     * Make sure that the upper bounds we got so far lead to a solvable inference
-     * variable by making sure that a glb exists.
-     */
-    void checkCompatibleUpperBounds(UndetVar uv, InferenceContext inferenceContext) {
-        List<Type> hibounds =
-                Type.filter(uv.getBounds(InferenceBound.UPPER), new BoundFilter(inferenceContext));
-        Type hb = null;
-        if (hibounds.isEmpty())
-            hb = syms.objectType;
-        else if (hibounds.tail.isEmpty())
-            hb = hibounds.head;
-        else
-            hb = types.glb(hibounds);
-        if (hb == null || hb.isErroneous())
-            reportBoundError(uv, BoundErrorKind.BAD_UPPER);
+    protected static class BoundFilter implements Filter<Type> {
+
+        InferenceContext inferenceContext;
+
+        public BoundFilter(InferenceContext inferenceContext) {
+            this.inferenceContext = inferenceContext;
+        }
+
+        @Override
+        public boolean accepts(Type t) {
+            return !t.isErroneous() && !inferenceContext.free(t) &&
+                    !t.hasTag(BOT);
+        }
     }
-    //where
-        protected static class BoundFilter implements Filter<Type> {
-
-            InferenceContext inferenceContext;
-
-            public BoundFilter(InferenceContext inferenceContext) {
-                this.inferenceContext = inferenceContext;
-            }
-
-            @Override
-            public boolean accepts(Type t) {
-                return !t.isErroneous() && !inferenceContext.free(t) &&
-                        !t.hasTag(BOT);
-            }
-        }
 
     /**
-     * This enumeration defines all possible bound-checking related errors.
+     * Incorporation error: mismatch between inferred type and given bound.
      */
-    enum BoundErrorKind {
-        /**
-         * The (uninstantiated) inference variable has incompatible upper bounds.
-         */
-        BAD_UPPER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.upper.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.UPPER));
-            }
-        },
-        /**
-         * The (uninstantiated) inference variable has incompatible equality constraints.
-         */
-        BAD_EQ() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.eq.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.EQ));
-            }
-        },
-        /**
-         * The (uninstantiated) inference variable has incompatible upper lower bounds.
-         */
-        BAD_UPPER_LOWER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.upper.lower.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.UPPER), uv.getBounds(InferenceBound.LOWER));
-            }
-        },
-        /**
-         * The (uninstantiated) inference variable has incompatible upper equal bounds.
-         */
-        BAD_UPPER_EQUAL() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.upper.eq.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.UPPER), uv.getBounds(InferenceBound.EQ));
-            }
-        },
-        /**
-         * The (uninstantiated) inference variable has incompatible upper equal bounds.
-         */
-        BAD_EQUAL_LOWER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.eq.lower.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.EQ), uv.getBounds(InferenceBound.LOWER));
-            }
-        },
-        /**
-         * An equality constraint is not compatible with an upper bound.
-         */
-        BAD_EQ_UPPER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.eq.upper.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.EQ), uv.getBounds(InferenceBound.UPPER));
-            }
-        },
-        /**
-         * An equality constraint is not compatible with a lower bound.
-         */
-        BAD_EQ_LOWER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("incompatible.eq.lower.bounds", uv.qtype,
-                        uv.getBounds(InferenceBound.EQ), uv.getBounds(InferenceBound.LOWER));
-            }
-        },
-        /**
-         * Instantiated inference variable is not compatible with an upper bound.
-         */
-        UPPER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("inferred.do.not.conform.to.upper.bounds", uv.inst,
-                        uv.getBounds(InferenceBound.UPPER));
-            }
-        },
-        /**
-         * Instantiated inference variable is not compatible with a lower bound.
-         */
-        LOWER() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("inferred.do.not.conform.to.lower.bounds", uv.inst,
-                        uv.getBounds(InferenceBound.LOWER));
-            }
-        },
-        /**
-         * Instantiated inference variable is not compatible with an equality constraint.
-         */
-        EQ() {
-            @Override
-            InapplicableMethodException setMessage(InferenceException ex, UndetVar uv) {
-                return ex.setMessage("inferred.do.not.conform.to.eq.bounds", uv.inst,
-                        uv.getBounds(InferenceBound.EQ));
-            }
-        };
-
-        abstract InapplicableMethodException setMessage(InferenceException ex, UndetVar uv);
+    void reportInstError(UndetVar uv, InferenceBound ib) {
+        reportInferenceError(
+                String.format("inferred.do.not.conform.to.%s.bounds", StringUtils.toLowerCase(ib.name())),
+                uv.getInst(),
+                uv.getBounds(ib));
     }
 
     /**
-     * Report a bound-checking error of given kind
+     * Incorporation error: mismatch between two (or more) bounds of same kind.
+     */
+    void reportBoundError(UndetVar uv, InferenceBound ib) {
+        reportInferenceError(
+                String.format("incompatible.%s.bounds", StringUtils.toLowerCase(ib.name())),
+                uv.qtype,
+                uv.getBounds(ib));
+    }
+
+    /**
+     * Incorporation error: mismatch between two (or more) bounds of different kinds.
      */
-    void reportBoundError(UndetVar uv, BoundErrorKind bk) {
-        throw bk.setMessage(inferenceException, uv);
+    void reportBoundError(UndetVar uv, InferenceBound ib1, InferenceBound ib2) {
+        reportInferenceError(
+                String.format("incompatible.%s.%s.bounds",
+                        StringUtils.toLowerCase(ib1.name()),
+                        StringUtils.toLowerCase(ib2.name())),
+                uv.qtype,
+                uv.getBounds(ib1),
+                uv.getBounds(ib2));
+    }
+
+    /**
+     * Helper method: reports an inference error.
+     */
+    void reportInferenceError(String key, Object... args) {
+        throw inferenceException.setMessage(key, args);
     }
     // </editor-fold>
 
@@ -1502,41 +1306,6 @@
             }
             return g.nodes.get(0);
         }
-
-        boolean isSubtype(Type s, Type t, Warner warn, Infer infer) {
-            return doIncorporationOp(IncorporationBinaryOpKind.IS_SUBTYPE, s, t, warn, infer);
-        }
-
-        boolean isSameType(Type s, Type t, Infer infer) {
-            return doIncorporationOp(IncorporationBinaryOpKind.IS_SAME_TYPE, s, t, null, infer);
-        }
-
-        void addBound(InferenceBound ib, UndetVar uv, Type b, Infer infer) {
-            doIncorporationOp(opFor(ib), uv, b, null, infer);
-        }
-
-        IncorporationBinaryOpKind opFor(InferenceBound boundKind) {
-            switch (boundKind) {
-                case EQ:
-                    return IncorporationBinaryOpKind.ADD_EQ_BOUND;
-                case LOWER:
-                    return IncorporationBinaryOpKind.ADD_LOWER_BOUND;
-                case UPPER:
-                    return IncorporationBinaryOpKind.ADD_UPPER_BOUND;
-                default:
-                    Assert.error("Can't get here!");
-                    return null;
-            }
-        }
-
-        boolean doIncorporationOp(IncorporationBinaryOpKind opKind, Type op1, Type op2, Warner warn, Infer infer) {
-            IncorporationBinaryOp newOp = infer.new IncorporationBinaryOp(opKind, op1, op2);
-            Boolean res = infer.incorporationCache.get(newOp);
-            if (res == null) {
-                infer.incorporationCache.put(newOp, res = newOp.apply(warn));
-            }
-            return res;
-        }
     }
 
     /**
@@ -1856,7 +1625,7 @@
          * depends on the selected solver strategy.
          */
         void solve(GraphStrategy sstrategy) {
-            checkWithinBounds(inferenceContext, warn); //initial propagation of bounds
+            doIncorporation(inferenceContext, warn); //initial propagation of bounds
             InferenceGraph inferenceGraph = new InferenceGraph(stuckDeps);
             while (!sstrategy.done()) {
                 if (dependenciesFolder != null) {
@@ -1871,8 +1640,8 @@
                     outer: while (Type.containsAny(inferenceContext.restvars(), varsToSolve)) {
                         //for each inference phase
                         for (GraphInferenceSteps step : GraphInferenceSteps.values()) {
-                            if (inferenceContext.solveBasic(varsToSolve, step.steps)) {
-                                checkWithinBounds(inferenceContext, warn);
+                            if (inferenceContext.solveBasic(varsToSolve, step.steps).nonEmpty()) {
+                                doIncorporation(inferenceContext, warn);
                                 continue outer;
                             }
                         }
@@ -1884,7 +1653,7 @@
                     //did we fail because of interdependent ivars?
                     inferenceContext.rollback(saved_undet);
                     instantiateAsUninferredVars(varsToSolve, inferenceContext);
-                    checkWithinBounds(inferenceContext, warn);
+                    doIncorporation(inferenceContext, warn);
                 }
                 inferenceGraph.deleteNode(nodeToSolve);
             }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java	Thu Nov 19 16:43:11 2015 +0000
@@ -39,6 +39,7 @@
 import com.sun.tools.javac.code.Type.UndetVar;
 import com.sun.tools.javac.code.Type.UndetVar.InferenceBound;
 import com.sun.tools.javac.code.Type.WildcardType;
+import com.sun.tools.javac.code.TypeTag;
 import com.sun.tools.javac.code.Types;
 import com.sun.tools.javac.comp.Infer.FreeTypeListener;
 import com.sun.tools.javac.comp.Infer.GraphSolver;
@@ -52,8 +53,6 @@
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Warner;
 
-import static com.sun.tools.javac.code.TypeTag.UNDETVAR;
-
 /**
  * An inference context keeps track of the set of variables that are free
  * in the current context. It provides utility methods for opening/closing
@@ -118,7 +117,7 @@
     List<Type> restvars() {
         return filterVars(new Filter<UndetVar>() {
             public boolean accepts(UndetVar uv) {
-                return uv.inst == null;
+                return uv.getInst() == null;
             }
         });
     }
@@ -130,7 +129,7 @@
     List<Type> instvars() {
         return filterVars(new Filter<UndetVar>() {
             public boolean accepts(UndetVar uv) {
-                return uv.inst != null;
+                return uv.getInst() != null;
             }
         });
     }
@@ -224,7 +223,7 @@
         ListBuffer<Type> buf = new ListBuffer<>();
         for (Type t : undetvars) {
             UndetVar uv = (UndetVar)t;
-            buf.append(uv.inst != null ? uv.inst : uv.qtype);
+            buf.append(uv.getInst() != null ? uv.getInst() : uv.qtype);
         }
         return buf.toList();
     }
@@ -289,15 +288,7 @@
     List<Type> save() {
         ListBuffer<Type> buf = new ListBuffer<>();
         for (Type t : undetvars) {
-            UndetVar uv = (UndetVar)t;
-            UndetVar uv2 = new UndetVar((TypeVar)uv.qtype, types);
-            for (InferenceBound ib : InferenceBound.values()) {
-                for (Type b : uv.getBounds(ib)) {
-                    uv2.addBound(ib, b, types);
-                }
-            }
-            uv2.inst = uv.inst;
-            buf.add(uv2);
+            buf.add(((UndetVar)t).dup(infer.types));
         }
         return buf.toList();
     }
@@ -315,10 +306,7 @@
             UndetVar uv = (UndetVar)undetvars.head;
             UndetVar uv_saved = (UndetVar)saved_undet.head;
             if (uv.qtype == uv_saved.qtype) {
-                for (InferenceBound ib : InferenceBound.values()) {
-                    uv.setBounds(ib, uv_saved.getBounds(ib));
-                }
-                uv.inst = uv_saved.inst;
+                uv_saved.dupTo(uv, types);
                 undetvars = undetvars.tail;
                 saved_undet = saved_undet.tail;
                 newUndetVars.add(uv);
@@ -367,7 +355,7 @@
         ListBuffer<Type> minUndetVars = new ListBuffer<>();
         for (Type minVar : minVars) {
             UndetVar uv = (UndetVar)asUndetVar(minVar);
-            UndetVar uv2 = new UndetVar((TypeVar)minVar, types);
+            UndetVar uv2 = new UndetVar((TypeVar)minVar, infer.incorporationEngine(), types);
             for (InferenceBound ib : InferenceBound.values()) {
                 List<Type> newBounds = uv.getBounds(ib).stream()
                         .filter(b -> !redundantVars.contains(b))
@@ -416,7 +404,7 @@
                 Set<Type> deps = minMap.getOrDefault(t.qtype, new HashSet<>(Collections.singleton(t.qtype)));
                 for (Type b : t.getBounds(InferenceBound.values())) {
                     Type undet = asUndetVar(b);
-                    if (!undet.hasTag(UNDETVAR)) {
+                    if (!undet.hasTag(TypeTag.UNDETVAR)) {
                         visit(undet);
                     } else if (isEquiv((UndetVar)undet, b)){
                         deps.add(b);
@@ -438,7 +426,7 @@
         @Override
         public Void visitTypeVar(TypeVar t, Void aVoid) {
             Type undet = asUndetVar(t);
-            if (undet.hasTag(UNDETVAR)) {
+            if (undet.hasTag(TypeTag.UNDETVAR)) {
                 visitUndetVar((UndetVar)undet, null);
             }
             return null;
@@ -519,23 +507,23 @@
     /**
      * Apply a set of inference steps
      */
-    private boolean solveBasic(EnumSet<InferenceStep> steps) {
+    private List<Type> solveBasic(EnumSet<InferenceStep> steps) {
         return solveBasic(inferencevars, steps);
     }
 
-    boolean solveBasic(List<Type> varsToSolve, EnumSet<InferenceStep> steps) {
-        boolean changed = false;
+    List<Type> solveBasic(List<Type> varsToSolve, EnumSet<InferenceStep> steps) {
+        ListBuffer<Type> solvedVars = new ListBuffer<>();
         for (Type t : varsToSolve.intersect(restvars())) {
             UndetVar uv = (UndetVar)asUndetVar(t);
             for (InferenceStep step : steps) {
                 if (step.accepts(uv, this)) {
-                    uv.inst = step.solve(uv, this);
-                    changed = true;
+                    uv.setInst(step.solve(uv, this));
+                    solvedVars.add(uv.qtype);
                     break;
                 }
             }
         }
-        return changed;
+        return solvedVars.toList();
     }
 
     /**
@@ -547,11 +535,11 @@
      */
     public void solveLegacy(boolean partial, Warner warn, EnumSet<InferenceStep> steps) {
         while (true) {
-            boolean stuck = !solveBasic(steps);
+            List<Type> solvedVars = solveBasic(steps);
             if (restvars().isEmpty() || partial) {
                 //all variables have been instantiated - exit
                 break;
-            } else if (stuck) {
+            } else if (solvedVars.isEmpty()) {
                 //some variables could not be instantiated because of cycles in
                 //upper bounds - provide a (possibly recursive) default instantiation
                 infer.instantiateAsUninferredVars(restvars(), this);
@@ -561,11 +549,11 @@
                 //variables in remaining upper bounds and continue
                 for (Type t : undetvars) {
                     UndetVar uv = (UndetVar)t;
-                    uv.substBounds(inferenceVars(), instTypes(), types);
+                    uv.substBounds(solvedVars, asInstTypes(solvedVars), types);
                 }
             }
         }
-        infer.checkWithinBounds(this, warn);
+        infer.doIncorporation(this, warn);
     }
 
     @Override
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Thu Nov 19 16:43:11 2015 +0000
@@ -1982,18 +1982,6 @@
     lower bounds: {2}
 
 # 0: type, 1: list of type, 2: list of type
-compiler.misc.incompatible.upper.eq.bounds=\
-    inference variable {0} has incompatible bounds\n\
-    upper bounds: {1}\n\
-    equality constraints: {2}
-
-# 0: type, 1: list of type, 2: list of type
-compiler.misc.incompatible.eq.lower.bounds=\
-    inference variable {0} has incompatible bounds\n\
-    equality constraints: {1}\n\
-    lower bounds: {2}
-
-# 0: type, 1: list of type, 2: list of type
 compiler.misc.incompatible.eq.lower.bounds=\
     inference variable {0} has incompatible bounds\n\
     equality constraints: {1}\n\
--- a/langtools/test/tools/javac/diags/examples.not-yet.txt	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/test/tools/javac/diags/examples.not-yet.txt	Thu Nov 19 16:43:11 2015 +0000
@@ -114,4 +114,3 @@
 compiler.err.cant.inherit.from.anon                     # error for subclass of anonymous class
 compiler.misc.bad.class.file                            # class file is malformed
 compiler.misc.bad.const.pool.entry                      # constant pool entry has wrong type
-compiler.misc.incompatible.upper.eq.bounds
--- a/langtools/test/tools/javac/generics/inference/7154127/T7154127.out	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/test/tools/javac/generics/inference/7154127/T7154127.out	Thu Nov 19 16:43:11 2015 +0000
@@ -1,2 +1,2 @@
-T7154127.java:20:49: compiler.err.prob.found.req: (compiler.misc.incompatible.upper.bounds: Y, T7154127.B<U>,T7154127.D)
+T7154127.java:20:49: compiler.err.prob.found.req: (compiler.misc.incompatible.upper.bounds: U, T7154127.B<Y>,T7154127.E)
 1 error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/generics/inference/8067767/T8067767.java	Thu Nov 19 16:43:11 2015 +0000
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8067767
+ * @summary type inference performance regression
+ * @compile T8067767.java
+ */
+class T8067767 {
+
+    static class Pair<A, B> {
+        static <A, B> Pair<A, B> of(A a, B b) {
+            throw new RuntimeException();
+        }
+    }
+
+    static class List<T> {
+        static <T> List<T> of(T... tx) {
+            throw new RuntimeException();
+        }
+    }
+
+    static final List<Pair<String, String>> PAIRS = List.of(
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"),
+            Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"), Pair.of("a", "b"));
+}
--- a/langtools/test/tools/javac/lambda/TargetType28.out	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/test/tools/javac/lambda/TargetType28.out	Thu Nov 19 16:43:11 2015 +0000
@@ -1,2 +1,2 @@
-TargetType28.java:20:32: compiler.err.prob.found.req: (compiler.misc.incompatible.eq.upper.bounds: X, R,java.lang.String, java.lang.Object,java.lang.Number)
+TargetType28.java:20:32: compiler.err.prob.found.req: (compiler.misc.incompatible.eq.upper.bounds: X, java.lang.String, java.lang.Object,java.lang.Number)
 1 error
--- a/langtools/test/tools/javac/lib/DPrinter.java	Thu Nov 19 17:19:06 2015 +0530
+++ b/langtools/test/tools/javac/lib/DPrinter.java	Thu Nov 19 16:43:11 2015 +0000
@@ -1304,7 +1304,7 @@
             for (UndetVar.InferenceBound ib: UndetVar.InferenceBound.values())
                 printList("bounds." + ib, type.getBounds(ib));
             printInt("declaredCount", type.declaredCount);
-            printType("inst", type.inst, Details.SUMMARY);
+            printType("inst", type.getInst(), Details.SUMMARY);
             return visitDelegatedType(type);
         }