< prev index next >

src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java

Print this page




1461         }
1462 
1463     }
1464 
1465 
1466     /**
1467      * <p><b>{@link Strategy#MH_INLINE_SIZED_EXACT}: "MethodHandles inline,
1468      * sized exactly".</b>
1469      *
1470      * <p>This strategy replicates what StringBuilders are doing: it builds the
1471      * byte[] array on its own and passes that byte[] array to String
1472      * constructor. This strategy requires access to some private APIs in JDK,
1473      * most notably, the read-only Integer/Long.stringSize methods that measure
1474      * the character length of the integers, and the private String constructor
1475      * that accepts byte[] arrays without copying. While this strategy assumes a
1476      * particular implementation details for String, this opens the door for
1477      * building a very optimal concatenation sequence. This is the only strategy
1478      * that requires porting if there are private JDK changes occur.
1479      */
1480     private static final class MethodHandleInlineCopyStrategy {

1481 
1482         private MethodHandleInlineCopyStrategy() {
1483             // no instantiation
1484         }
1485 
1486         static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
1487 
1488             // Create filters and obtain filtered parameter types. Filters would be used in the beginning
1489             // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
1490             // The filtered argument type list is used all over in the combinators below.
1491             Class<?>[] ptypes = mt.parameterArray();
1492             MethodHandle[] filters = null;
1493             for (int i = 0; i < ptypes.length; i++) {
1494                 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1495                 if (filter != null) {
1496                     if (filters == null) {
1497                         filters = new MethodHandle[ptypes.length];
1498                     }
1499                     filters[i] = filter;
1500                     ptypes[i] = filter.type().returnType();
1501                 }
1502             }
1503             List<Class<?>> ptypesList = Arrays.asList(ptypes);
1504 
1505             // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1506             // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,
1507             // which makes the code arguably hard to read.
1508 
1509             // Drop all remaining parameter types, leave only helper arguments:
1510             MethodHandle mh;
1511 
1512             mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1513             mh = MethodHandles.dropArguments(mh, 0, int.class);
1514 
1515             // In debug mode, check that remaining index is zero.
1516             if (DEBUG) {
1517                 mh = MethodHandles.filterArgument(mh, 0, CHECK_INDEX);
1518             }
1519 
1520             // Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already
1521             // known from the combinators below. We are assembling the string backwards, so "index" is the
1522             // *ending* index.
1523             for (RecipeElement el : recipe.getElements()) {
1524                 MethodHandle prepender;
1525                 switch (el.getTag()) {
1526                     case CONST: {
1527                         Object cnst = el.getValue();
1528                         prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
1529                         break;
1530                     }
1531                     case ARG: {
1532                         int pos = el.getArgPos();
1533                         prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos);
1534                         break;
1535                     }
1536                     default:
1537                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1538                 }


1633             for (int i = 2; i < count; i++) {
1634                 perm[i] = i;
1635             }
1636             return perm;
1637         }
1638 
1639         // Adapts: (...prefix..., parameter[pos])R -> (...prefix..., ...parameters...)R
1640         private static MethodHandle selectArgument(MethodHandle mh, int prefix, List<Class<?>> ptypes, int pos) {
1641             if (pos == 0) {
1642                 return MethodHandles.dropArguments(mh, prefix + 1, ptypes.subList(1, ptypes.size()));
1643             } else if (pos == ptypes.size() - 1) {
1644                 return MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, ptypes.size() - 1));
1645             } else { // 0 < pos < ptypes.size() - 1
1646                 MethodHandle t = MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, pos));
1647                 return MethodHandles.dropArguments(t, prefix + 1 + pos, ptypes.subList(pos + 1, ptypes.size()));
1648             }
1649         }
1650 
1651         @ForceInline
1652         private static byte[] newArray(int length, byte coder) {
1653             return new byte[length << coder];
1654         }
1655 
1656         @ForceInline
1657         private static int checkIndex(int index) {
1658             if (index != 0) {
1659                 throw new AssertionError("Exactness check failed: " + index + " characters left in the buffer.");
1660             }
1661             return index;
1662         }
1663 
1664         private static MethodHandle prepender(Class<?> cl) {
1665             return PREPENDERS.computeIfAbsent(cl, PREPEND);
1666         }
1667 
1668         private static MethodHandle coderMixer(Class<?> cl) {
1669             return CODER_MIXERS.computeIfAbsent(cl, CODER_MIX);
1670         }
1671 
1672         private static MethodHandle lengthMixer(Class<?> cl) {
1673             return LENGTH_MIXERS.computeIfAbsent(cl, LENGTH_MIX);
1674         }
1675 
1676         // This one is deliberately non-lambdified to optimize startup time:
1677         private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
1678             @Override
1679             public MethodHandle apply(Class<?> c) {


1704         private static final ConcurrentMap<Class<?>, MethodHandle> LENGTH_MIXERS;
1705         private static final ConcurrentMap<Class<?>, MethodHandle> CODER_MIXERS;
1706         private static final Class<?> STRING_HELPER;
1707         private static final byte INITIAL_CODER;
1708 
1709         static {
1710             try {
1711                 STRING_HELPER = Class.forName("java.lang.StringConcatHelper");
1712                 MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", byte.class);
1713                 INITIAL_CODER = (byte) initCoder.invoke();
1714             } catch (Throwable e) {
1715                 throw new AssertionError(e);
1716             }
1717 
1718             PREPENDERS = new ConcurrentHashMap<>();
1719             LENGTH_MIXERS = new ConcurrentHashMap<>();
1720             CODER_MIXERS = new ConcurrentHashMap<>();
1721 
1722             NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, byte.class);
1723             NEW_ARRAY  = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "newArray", byte[].class, int.class, byte.class);
1724 
1725             if (DEBUG) {
1726                 CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class);
1727             } else {
1728                 CHECK_INDEX = null;
1729             }
1730         }
1731     }
1732 
1733     /**
1734      * Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
1735      * delegate to {@code String.valueOf}, depending on argument's type.
1736      */
1737     private static final class Stringifiers {
1738         private Stringifiers() {
1739             // no instantiation
1740         }
1741 
1742         // This one is deliberately non-lambdified to optimize startup time:
1743         private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() {
1744             @Override
1745             public MethodHandle apply(Class<?> cl) {
1746                 MethodHandle mhObject = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class);
1747 
1748                 // We need the additional conversion here, because String.valueOf(Object) may return null.
1749                 // String conversion rules in Java state we need to produce "null" String in this case.




1461         }
1462 
1463     }
1464 
1465 
1466     /**
1467      * <p><b>{@link Strategy#MH_INLINE_SIZED_EXACT}: "MethodHandles inline,
1468      * sized exactly".</b>
1469      *
1470      * <p>This strategy replicates what StringBuilders are doing: it builds the
1471      * byte[] array on its own and passes that byte[] array to String
1472      * constructor. This strategy requires access to some private APIs in JDK,
1473      * most notably, the read-only Integer/Long.stringSize methods that measure
1474      * the character length of the integers, and the private String constructor
1475      * that accepts byte[] arrays without copying. While this strategy assumes a
1476      * particular implementation details for String, this opens the door for
1477      * building a very optimal concatenation sequence. This is the only strategy
1478      * that requires porting if there are private JDK changes occur.
1479      */
1480     private static final class MethodHandleInlineCopyStrategy {
1481         static final Unsafe UNSAFE = Unsafe.getUnsafe();
1482 
1483         private MethodHandleInlineCopyStrategy() {
1484             // no instantiation
1485         }
1486 
1487         static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
1488 
1489             // Create filters and obtain filtered parameter types. Filters would be used in the beginning
1490             // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
1491             // The filtered argument type list is used all over in the combinators below.
1492             Class<?>[] ptypes = mt.parameterArray();
1493             MethodHandle[] filters = null;
1494             for (int i = 0; i < ptypes.length; i++) {
1495                 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1496                 if (filter != null) {
1497                     if (filters == null) {
1498                         filters = new MethodHandle[ptypes.length];
1499                     }
1500                     filters[i] = filter;
1501                     ptypes[i] = filter.type().returnType();
1502                 }
1503             }
1504             List<Class<?>> ptypesList = Arrays.asList(ptypes);
1505 
1506             // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1507             // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,
1508             // which makes the code arguably hard to read.
1509 
1510             // Drop all remaining parameter types, leave only helper arguments:
1511             MethodHandle mh;
1512 
1513             mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1514             mh = MethodHandles.dropArguments(mh, 0, int.class);
1515 
1516             // Safety: check that remaining index is zero -- that would mean the storage is completely
1517             // overwritten, and no leakage of uninitialized data occurred.
1518             mh = MethodHandles.filterArgument(mh, 0, CHECK_INDEX);

1519 
1520             // Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already
1521             // known from the combinators below. We are assembling the string backwards, so "index" is the
1522             // *ending* index.
1523             for (RecipeElement el : recipe.getElements()) {
1524                 MethodHandle prepender;
1525                 switch (el.getTag()) {
1526                     case CONST: {
1527                         Object cnst = el.getValue();
1528                         prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
1529                         break;
1530                     }
1531                     case ARG: {
1532                         int pos = el.getArgPos();
1533                         prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos);
1534                         break;
1535                     }
1536                     default:
1537                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1538                 }


1633             for (int i = 2; i < count; i++) {
1634                 perm[i] = i;
1635             }
1636             return perm;
1637         }
1638 
1639         // Adapts: (...prefix..., parameter[pos])R -> (...prefix..., ...parameters...)R
1640         private static MethodHandle selectArgument(MethodHandle mh, int prefix, List<Class<?>> ptypes, int pos) {
1641             if (pos == 0) {
1642                 return MethodHandles.dropArguments(mh, prefix + 1, ptypes.subList(1, ptypes.size()));
1643             } else if (pos == ptypes.size() - 1) {
1644                 return MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, ptypes.size() - 1));
1645             } else { // 0 < pos < ptypes.size() - 1
1646                 MethodHandle t = MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, pos));
1647                 return MethodHandles.dropArguments(t, prefix + 1 + pos, ptypes.subList(pos + 1, ptypes.size()));
1648             }
1649         }
1650 
1651         @ForceInline
1652         private static byte[] newArray(int length, byte coder) {
1653             return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
1654         }
1655 
1656         @ForceInline
1657         private static int checkIndex(int index) {
1658             if (index != 0) {
1659                 throw new IllegalStateException("Storage is not completely initialized, " + index + " bytes left");
1660             }
1661             return index;
1662         }
1663 
1664         private static MethodHandle prepender(Class<?> cl) {
1665             return PREPENDERS.computeIfAbsent(cl, PREPEND);
1666         }
1667 
1668         private static MethodHandle coderMixer(Class<?> cl) {
1669             return CODER_MIXERS.computeIfAbsent(cl, CODER_MIX);
1670         }
1671 
1672         private static MethodHandle lengthMixer(Class<?> cl) {
1673             return LENGTH_MIXERS.computeIfAbsent(cl, LENGTH_MIX);
1674         }
1675 
1676         // This one is deliberately non-lambdified to optimize startup time:
1677         private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
1678             @Override
1679             public MethodHandle apply(Class<?> c) {


1704         private static final ConcurrentMap<Class<?>, MethodHandle> LENGTH_MIXERS;
1705         private static final ConcurrentMap<Class<?>, MethodHandle> CODER_MIXERS;
1706         private static final Class<?> STRING_HELPER;
1707         private static final byte INITIAL_CODER;
1708 
1709         static {
1710             try {
1711                 STRING_HELPER = Class.forName("java.lang.StringConcatHelper");
1712                 MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", byte.class);
1713                 INITIAL_CODER = (byte) initCoder.invoke();
1714             } catch (Throwable e) {
1715                 throw new AssertionError(e);
1716             }
1717 
1718             PREPENDERS = new ConcurrentHashMap<>();
1719             LENGTH_MIXERS = new ConcurrentHashMap<>();
1720             CODER_MIXERS = new ConcurrentHashMap<>();
1721 
1722             NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, byte.class);
1723             NEW_ARRAY  = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "newArray", byte[].class, int.class, byte.class);


1724             CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class);



1725         }
1726     }
1727 
1728     /**
1729      * Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
1730      * delegate to {@code String.valueOf}, depending on argument's type.
1731      */
1732     private static final class Stringifiers {
1733         private Stringifiers() {
1734             // no instantiation
1735         }
1736 
1737         // This one is deliberately non-lambdified to optimize startup time:
1738         private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() {
1739             @Override
1740             public MethodHandle apply(Class<?> cl) {
1741                 MethodHandle mhObject = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class);
1742 
1743                 // We need the additional conversion here, because String.valueOf(Object) may return null.
1744                 // String conversion rules in Java state we need to produce "null" String in this case.


< prev index next >