changeset 49305:a6b463a66e0c switch

First attempt to add switch over booleans.
author jlahoda
date Mon, 26 Feb 2018 16:57:24 +0100
parents 00909878a9f3
children f7e0fb7b2ce4
files src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java test/jdk/java/lang/runtime/TestSwitchBootstrap.java test/langtools/tools/javac/switchextra/SwitchBooleanExhaustivness.java test/langtools/tools/javac/switchextra/SwitchBooleanExhaustivness.out test/langtools/tools/javac/switchextra/SwitchExtra.java
diffstat 11 files changed, 236 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java	Mon Feb 26 16:57:24 2018 +0100
@@ -57,6 +57,8 @@
     private static final MethodHandle INIT_HOOK;
     private static final Map<Class<?>, MethodHandle> switchMethods = new ConcurrentHashMap<>();
 
+    private static final Set<Class<?>> BOOLEAN_TYPES
+            = Set.of(boolean.class, Boolean.class);
     // Types that can be handled as int switches
     private static final Set<Class<?>> INT_TYPES
             = Set.of(int.class, short.class, byte.class, char.class,
@@ -79,7 +81,8 @@
                             switchClass = EnumSwitchCallSite.class;
                         else if (c == String.class)
                             switchClass = StringSwitchCallSite.class;
-                        else if (INT_TYPES.contains(c) || FLOAT_TYPES.contains(c))
+                        else if (BOOLEAN_TYPES.contains(c) || INT_TYPES.contains(c) ||
+                                 FLOAT_TYPES.contains(c))
                             switchClass = IntSwitchCallSite.class;
                         else if (LONG_TYPES.contains(c) || DOUBLE_TYPES.contains(c))
                             switchClass = LongSwitchCallSite.class;
@@ -112,6 +115,48 @@
 
     /**
      * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code boolean} or {@code Boolean}.
+     * The static arguments are a varargs array of {@code boolean} labels.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter which is
+     *                       {@code boolean} or {@code Boolean},and return {@code int}.
+     *                       When used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param booleanLabels boolean values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code booleanLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code booleanLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite booleanSwitch(MethodHandles.Lookup lookup,
+                                         String invocationName,
+                                         MethodType invocationType,
+                                         boolean... booleanLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!BOOLEAN_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+
+        int[] intLabels = new int[booleanLabels.length];
+        for (int i=0; i<booleanLabels.length; i++)
+            intLabels[i] = booleanLabels[i] ? 1 : 0;
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
      * implements a {@code switch} on an {@code int}, {@code short}, {@code byte},
      * {@code char}, or one of their box types.  The static arguments are a
      * varargs array of {@code int} labels.
@@ -212,6 +257,11 @@
                 labels[i] = intLabels[indexes[i]];
         }
 
+        int doSwitch(boolean target) {
+            int index = Arrays.binarySearch(labels, target ? 1 : 0);
+            return (index >= 0) ? indexes[index] : indexes.length;
+        }
+
         int doSwitch(int target) {
             int index = Arrays.binarySearch(labels, target);
             return (index >= 0) ? indexes[index] : indexes.length;
@@ -233,6 +283,10 @@
             return doSwitch((int) target);
         }
 
+        int doSwitch(Boolean target) {
+            return (target == null) ? -1 : doSwitch((boolean) target);
+        }
+
         int doSwitch(Integer target) {
             return (target == null) ? -1 : doSwitch((int) target);
         }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java	Mon Feb 26 16:57:24 2018 +0100
@@ -210,6 +210,7 @@
     public final Type lambdaMetafactory;
     public final Type stringConcatFactory;
     public final Type switchBootstraps;
+    public final Type constantBootstraps;
     public final Type repeatableType;
     public final Type documentedType;
     public final Type elementTypeType;
@@ -549,6 +550,7 @@
         lambdaMetafactory = enterClass("java.lang.invoke.LambdaMetafactory");
         stringConcatFactory = enterClass("java.lang.invoke.StringConcatFactory");
         switchBootstraps = enterClass("java.lang.runtime.SwitchBootstraps");
+        constantBootstraps = enterClass("java.lang.invoke.ConstantBootstraps");
         functionalInterfaceType = enterClass("java.lang.FunctionalInterface");
 
         synthesizeEmptyInterfaceIfMissing(autoCloseableType);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Feb 26 16:57:24 2018 +0100
@@ -1459,9 +1459,7 @@
                 log.error(DiagnosticFlag.SOURCE_LEVEL, selector.pos(), Feature.STRINGS_IN_SWITCH.error(sourceName));
             }
             Type unboxedSelType = types.unboxedTypeOrType(seltype);
-            if (!enumSwitch && !stringSwitch && !types.isSubtype(unboxedSelType, syms.intType) &&
-                !types.isSameType(unboxedSelType, syms.longType) && !types.isSubtype(unboxedSelType, syms.floatType) &&
-                !types.isSameType(unboxedSelType, syms.doubleType)) {
+            if (!enumSwitch && !stringSwitch && !unboxedSelType.isPrimitive()) {
                 log.error(selector.pos(), Errors.SwitchInvalidType(seltype));
             }
 
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java	Mon Feb 26 16:57:24 2018 +0100
@@ -600,14 +600,24 @@
             ListBuffer<PendingExit> prevPendingExits = pendingExits;
             pendingExits = new ListBuffer<>();
             scan(tree.selector);
+            Set<Object> constants = null;
+            if (types.unboxedTypeOrType(tree.selector.type).tsym == syms.booleanType.tsym) {
+                constants = new HashSet<>();
+                constants.add(0);
+                constants.add(1);
+            }
             boolean hasDefault = false;
             for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
                 alive = true;
                 JCCase c = l.head;
                 if (c.pat == null)
                     hasDefault = true;
-                else
+                else {
                     scan(c.pat);
+                    if (constants != null) {
+                        constants.remove(c.pat.type.constValue());
+                    }
+                }
                 scanStats(c.stats);
                 // Warn about fall-through if lint switch fallthrough enabled.
                 if (alive &&
@@ -617,7 +627,7 @@
                                 l.tail.head.pos(),
                                 Warnings.PossibleFallThroughIntoCase);
             }
-            if (!hasDefault) {
+            if ((constants == null || !constants.isEmpty()) && !hasDefault) {
                 alive = true;
             }
             alive |= resolveBreaks(tree, prevPendingExits);
@@ -628,13 +638,18 @@
             ListBuffer<PendingExit> prevPendingExits = pendingExits;
             pendingExits = new ListBuffer<>();
             scan(tree.selector);
-            Set<Name> constants = null;
+            Set<Object> constants = null;
             if ((tree.selector.type.tsym.flags() & ENUM) != 0) {
                 constants = new HashSet<>();
                 for (Symbol s : tree.selector.type.tsym.members().getSymbols(s -> (s.flags() & ENUM) != 0)) {
                     constants.add(s.name);
                 }
             }
+            if (types.unboxedTypeOrType(tree.selector.type).tsym == syms.booleanType.tsym) {
+                constants = new HashSet<>();
+                constants.add(0);
+                constants.add(1);
+            }
             boolean hasDefault = false;
             boolean prevAlive = alive;
             for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
@@ -644,8 +659,11 @@
                     hasDefault = true;
                 else {
                     scan(c.pat);
-                    if (constants != null && c.pat.hasTag(IDENT)) {
-                        constants.remove(((JCIdent) c.pat).name);
+                    if (constants != null) {
+                        if (c.pat.hasTag(IDENT))
+                            constants.remove(((JCIdent) c.pat).name);
+                        if (c.pat.type != null)
+                            constants.remove(c.pat.type.constValue());
                     }
                 }
                 scanStats(c.stats);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java	Mon Feb 26 16:57:24 2018 +0100
@@ -57,6 +57,7 @@
 import static com.sun.tools.javac.code.Symbol.OperatorSymbol.AccessCode.DEREF;
 import static com.sun.tools.javac.jvm.ByteCodes.*;
 import com.sun.tools.javac.tree.JCTree.JCCase.CaseKind;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
 import static com.sun.tools.javac.tree.JCTree.JCOperatorExpression.OperandPos.LEFT;
 import static com.sun.tools.javac.tree.JCTree.Tag.*;
 
@@ -3444,6 +3445,7 @@
             } else {
                 Name switchName;
                 Type methodType;
+                Function<JCExpression, Object> caseToValue = pat -> pat.type.constValue();
                 if (types.isSameType(unboxed, syms.longType)) {
                     switchName = names.longSwitch;
                     methodType = syms.longType;
@@ -3453,6 +3455,28 @@
                 } else if (types.isSameType(unboxed, syms.doubleType)) {
                     switchName = names.doubleSwitch;
                     methodType = syms.doubleType;
+                } else if (types.isSameType(unboxed, syms.booleanType)) {
+                    switchName = names.booleanSwitch;
+                    methodType = syms.booleanType;
+                    caseToValue = pat -> {
+                        Symbol getStaticFinal = rs.resolveInternalMethod(tree.pos(), attrEnv,
+                                syms.constantBootstraps, names.getStaticFinal,
+                                List.of(syms.methodHandleLookupType,
+                                        syms.stringType,
+                                        syms.classType,
+                                        syms.classType), List.nil());
+
+                        int value = (Integer) pat.type.constValue();
+                        Name valueName = value != 0 ? names.TRUE : names.FALSE;
+                        Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(valueName,
+                                syms.noSymbol,
+                                ClassFile.REF_invokeStatic,
+                                (Symbol.MethodSymbol)getStaticFinal,
+                                types.boxedClass(syms.booleanType).type,
+                                new Object[] {types.boxedClass(syms.booleanType)
+                                });
+                        return dynSym;
+                    };
                 } else {
                     switchName = names.intSwitch;
                     methodType = syms.intType;
@@ -3464,7 +3488,7 @@
                                                       tree.selector.type,
                                                       tree.selector.type,
                                                       false,
-                                                      pat -> pat.type.constValue());
+                                                      caseToValue);
             }
 
             tree.selector = make.Apply(List.nil(), qualifier, List.of(tree.selector));
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java	Mon Feb 26 16:57:24 2018 +0100
@@ -411,7 +411,7 @@
                     for (Object staticArg : dynSym.staticArgs) {
                         pool.put(staticArg);
                     }
-                    poolbuf.appendByte(CONSTANT_InvokeDynamic);
+                    poolbuf.appendByte(dynSym.type.hasTag(METHOD) ? CONSTANT_InvokeDynamic : CONSTANT_Dynamic);
                     poolbuf.appendChar(val.index);
                     poolbuf.appendChar(pool.put(nameType(dynSym)));
                 }
@@ -1109,7 +1109,7 @@
             databuf.appendChar(uniqueArgs.length);
             //write static args array
             for (Object o : uniqueArgs) {
-                databuf.appendChar(pool.get(o));
+                databuf.appendChar(pool.get(pool.makePoolValue(o)));
             }
         }
         endAttr(alenIdx);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java	Mon Feb 26 16:57:24 2018 +0100
@@ -206,10 +206,14 @@
     // switch
     public final Name stringSwitch;
     public final Name enumSwitch;
+    public final Name booleanSwitch;
     public final Name intSwitch;
     public final Name longSwitch;
     public final Name floatSwitch;
     public final Name doubleSwitch;
+    public final Name getStaticFinal;
+    public final Name TRUE;
+    public final Name FALSE;
 
     public final Name.Table table;
 
@@ -376,10 +380,14 @@
         //switch desugaring:
         stringSwitch = fromString("stringSwitch");
         enumSwitch = fromString("enumSwitch");
+        booleanSwitch = fromString("booleanSwitch");
         intSwitch = fromString("intSwitch");
         longSwitch = fromString("longSwitch");
         floatSwitch = fromString("floatSwitch");
         doubleSwitch = fromString("doubleSwitch");
+        getStaticFinal = fromString("getStaticFinal");
+        TRUE = fromString("TRUE");
+        FALSE = fromString("FALSE");
     }
 
     protected Name.Table createTable(Options options) {
--- a/test/jdk/java/lang/runtime/TestSwitchBootstrap.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/test/jdk/java/lang/runtime/TestSwitchBootstrap.java	Mon Feb 26 16:57:24 2018 +0100
@@ -52,6 +52,7 @@
  */
 @Test
 public class TestSwitchBootstrap {
+    private final static Set<Class<?>> BOOLEAN_TYPES = Set.of(boolean.class, Boolean.class);
     private final static Set<Class<?>> ALL_INT_TYPES = Set.of(int.class, short.class, byte.class, char.class,
                                                               Integer.class, Short.class, Byte.class, Character.class);
     private final static Set<Class<?>> SIGNED_NON_BYTE_TYPES = Set.of(int.class, Integer.class, short.class, Short.class);
@@ -61,6 +62,7 @@
             = Set.of(int.class, short.class, byte.class,
                      Integer.class, Short.class, Byte.class);
 
+    public static final MethodHandle BSM_BOOLEAN_SWITCH;
     public static final MethodHandle BSM_INT_SWITCH;
     public static final MethodHandle BSM_LONG_SWITCH;
     public static final MethodHandle BSM_FLOAT_SWITCH;
@@ -72,6 +74,8 @@
 
     static {
         try {
+            BSM_BOOLEAN_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "booleanSwitch",
+                                                                   MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, boolean[].class));
             BSM_INT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "intSwitch",
                                                                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int[].class));
             BSM_LONG_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "longSwitch",
@@ -107,6 +111,31 @@
             throw new IllegalArgumentException(clazz.toString());
     }
 
+    private void testBoolean(boolean... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(boolean.class), labels)).dynamicInvoker(),
+                         Boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Boolean.class), labels)).dynamicInvoker());
+
+        List<Boolean> labelList = new ArrayList<>();
+        for (boolean label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(boolean.class).invokeExact((boolean) labels[i]));
+            assertEquals(i, (int) mhs.get(Boolean.class).invokeExact((Boolean) labels[i]));
+        }
+
+        boolean[] booleans = { false, true };
+        for (boolean b : booleans) {
+            if (!labelList.contains(b)) {
+                assertEquals(labels.length, mhs.get(boolean.class).invoke((boolean) b));
+                assertEquals(labels.length, mhs.get(Boolean.class).invoke((boolean) b));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Boolean.class).invoke(null));
+    }
+
     private void testInt(Set<Class<?>> targetTypes, int... labels) throws Throwable {
         Map<Class<?>, MethodHandle> mhs
                 = Map.of(char.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(char.class), labels)).dynamicInvoker(),
@@ -287,6 +316,13 @@
         assertEquals(-1, (int) indy.invoke(null));
     }
 
+    public void testBoolean() throws Throwable {
+        testBoolean(new boolean[0]);
+        testBoolean(false);
+        testBoolean(true);
+        testBoolean(false, true);
+    }
+
     public void testInt() throws Throwable {
         testInt(ALL_INT_TYPES, 8, 6, 7, 5, 3, 0, 9);
         testInt(ALL_INT_TYPES, 1, 2, 4, 8, 16);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/switchextra/SwitchBooleanExhaustivness.java	Mon Feb 26 16:57:24 2018 +0100
@@ -0,0 +1,47 @@
+/*
+ * @test /nodynamiccopyright/
+ * @compile/fail/ref=SwitchBooleanExhaustivness.out -XDrawDiagnostics SwitchBooleanExhaustivness.java
+ */
+public class SwitchBooleanExhaustivness {
+
+    private int exhaustive1(boolean b) {
+        switch (b) {
+            case false: return 0;
+            case true: return 1;
+        }
+    }
+
+    private int exhaustive2(boolean b) {
+        switch (b) {
+            case false: return 0;
+            default: return 1;
+        }
+    }
+
+    private int exhaustive3(boolean b) {
+        return switch (b) {
+            case false: break 0;
+            case true: break 1;
+        };
+    }
+
+    private int exhaustive4(boolean b) {
+        return switch (b) {
+            case false: break 0;
+            default: break 1;
+        };
+    }
+
+    private int notExhaustive1(boolean b) {
+        switch (b) {
+            case false: return 0;
+        }
+    }
+
+    private int notExhaustive2(boolean b) {
+        return switch (b) {
+            case false: break 0;
+        };
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/switchextra/SwitchBooleanExhaustivness.out	Mon Feb 26 16:57:24 2018 +0100
@@ -0,0 +1,3 @@
+SwitchBooleanExhaustivness.java:39:5: compiler.err.missing.ret.stmt
+SwitchBooleanExhaustivness.java:42:16: compiler.err.not.exhaustive
+2 errors
--- a/test/langtools/tools/javac/switchextra/SwitchExtra.java	Wed Feb 21 08:20:31 2018 +0100
+++ b/test/langtools/tools/javac/switchextra/SwitchExtra.java	Mon Feb 26 16:57:24 2018 +0100
@@ -78,6 +78,12 @@
         assertEquals(5, doubleSwitchBoxed(Double.POSITIVE_INFINITY));
         assertEquals(6, doubleSwitchBoxed(0d));
         assertEquals(7, doubleSwitchBoxed(3.14));
+        assertEquals(0, booleanSwitch1(false));
+        assertEquals(1, booleanSwitch1(true));
+        assertEquals(0, booleanSwitch2(false));
+        assertEquals(1, booleanSwitch2(true));
+        assertEquals(0, booleanSwitchBoxed(false));
+        assertEquals(1, booleanSwitchBoxed(true));
     }
 
     private int longSwitch(long l) {
@@ -154,6 +160,34 @@
         }
     }
 
+    private int booleanSwitch1(boolean b) {
+        switch (b) {
+            case false: return 0;
+            case true: return 1;
+        }
+    }
+
+    private int booleanSwitch2(boolean b) {
+        switch (b) {
+            case false: return 0;
+            default: return 1;
+        }
+    }
+
+    private int booleanSwitchBoxed(Boolean b) {
+        switch (b) {
+            case false: return 0;
+            case true: return 1;
+        }
+    }
+
+    private int booleanSwitchExpr(boolean b) {
+        return switch (b) {
+            case false -> 0;
+            case true -> 1;
+        };
+    }
+
     private void assertEquals(int expected, int actual) {
         if (expected != actual) {
             throw new AssertionError();