< prev index next >

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

Print this page
rev 54622 : 8222852: Reduce String concat combinator tree shapes by folding constants into prependers
Reviewed-by: shade, plevart
Contributed-by: claes.redestad@oracle.com, peter.levart@gmail.com


  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import jdk.internal.misc.Unsafe;
  29 import jdk.internal.misc.VM;
  30 import jdk.internal.org.objectweb.asm.ClassWriter;
  31 import jdk.internal.org.objectweb.asm.Label;
  32 import jdk.internal.org.objectweb.asm.MethodVisitor;
  33 import jdk.internal.org.objectweb.asm.Opcodes;
  34 import jdk.internal.vm.annotation.ForceInline;
  35 import sun.invoke.util.Wrapper;
  36 import sun.security.action.GetPropertyAction;
  37 
  38 import java.lang.invoke.MethodHandles.Lookup;
  39 import java.util.ArrayList;
  40 import java.util.Arrays;
  41 import java.util.List;
  42 import java.util.Objects;
  43 import java.util.Properties;
  44 import java.util.concurrent.ConcurrentHashMap;
  45 import java.util.concurrent.ConcurrentMap;
  46 import java.util.function.Function;
  47 
  48 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  49 
  50 /**
  51  * <p>Methods to facilitate the creation of String concatenation methods, that
  52  * can be used to efficiently concatenate a known number of arguments of known
  53  * types, possibly after type adaptation and partial evaluation of arguments.
  54  * These methods are typically used as <em>bootstrap methods</em> for {@code
  55  * invokedynamic} call sites, to support the <em>string concatenation</em>
  56  * feature of the Java Programming Language.
  57  *
  58  * <p>Indirect access to the behavior specified by the provided {@code
  59  * MethodHandle} proceeds in order through two phases:
  60  *
  61  * <ol>
  62  *     <li><em>Linkage</em> occurs when the methods in this class are invoked.
  63  * They take as arguments a method type describing the concatenated arguments


1515      * the character length of the integers, and the private String constructor
1516      * that accepts byte[] arrays without copying. While this strategy assumes a
1517      * particular implementation details for String, this opens the door for
1518      * building a very optimal concatenation sequence. This is the only strategy
1519      * that requires porting if there are private JDK changes occur.
1520      */
1521     private static final class MethodHandleInlineCopyStrategy {
1522         static final Unsafe UNSAFE = Unsafe.getUnsafe();
1523 
1524         private MethodHandleInlineCopyStrategy() {
1525             // no instantiation
1526         }
1527 
1528         static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
1529 
1530             // Fast-path two-argument Object + Object concatenations
1531             if (recipe.getElements().size() == 2) {
1532                 // Two object arguments
1533                 if (mt.parameterCount() == 2 &&
1534                         !mt.parameterType(0).isPrimitive() &&
1535                         !mt.parameterType(1).isPrimitive()) {



1536                     return SIMPLE;
1537                 }
1538                 // One element is a constant
1539                 if (mt.parameterCount() == 1 && !mt.parameterType(0).isPrimitive()) {

1540                     MethodHandle mh = SIMPLE;
1541                     // Insert constant element
1542 
1543                     // First recipe element is a constant
1544                     if (recipe.getElements().get(0).getTag() == TAG_CONST &&
1545                         recipe.getElements().get(1).getTag() != TAG_CONST) {

1546                         return MethodHandles.insertArguments(mh, 0,
1547                                 recipe.getElements().get(0).getValue());

1548                     } else if (recipe.getElements().get(1).getTag() == TAG_CONST &&
1549                                recipe.getElements().get(0).getTag() != TAG_CONST) {

1550                         return MethodHandles.insertArguments(mh, 1,
1551                                 recipe.getElements().get(1).getValue());

1552                     }
1553                     // else... fall-through to slow-path
1554                 }

1555             }
1556 
1557             // Create filters and obtain filtered parameter types. Filters would be used in the beginning
1558             // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
1559             // The filtered argument type list is used all over in the combinators below.
1560             Class<?>[] ptypes = mt.parameterArray();
1561             MethodHandle[] filters = null;
1562             for (int i = 0; i < ptypes.length; i++) {
1563                 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1564                 if (filter != null) {
1565                     if (filters == null) {
1566                         filters = new MethodHandle[ptypes.length];
1567                     }
1568                     filters[i] = filter;
1569                     ptypes[i] = filter.type().returnType();
1570                 }
1571             }
1572 
1573             // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1574             // with the (byte[], long)String shape to invoke newString in StringConcatHelper. The combinators are
1575             // assembled bottom-up, which makes the code arguably hard to read.
1576 
1577             // Drop all remaining parameter types, leave only helper arguments:
1578             MethodHandle mh;
1579 
1580             mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1581 


1582             // Mix in prependers. This happens when (byte[], long) = (storage, indexCoder) is already
1583             // known from the combinators below. We are assembling the string backwards, so the index coded
1584             // into indexCoder is the *ending* index.






1585             for (RecipeElement el : recipe.getElements()) {
1586                 // Do the prepend, and put "new" index at index 1
1587                 switch (el.getTag()) {
1588                     case TAG_CONST: {
1589                         MethodHandle prepender = MethodHandles.insertArguments(prepender(String.class), 2, el.getValue());
1590                         mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,
1591                                 1, 0 // indexCoder, storage
1592                         );








1593                         break;
1594                     }
1595                     case TAG_ARG: {
1596                         int pos = el.getArgPos();
1597                         MethodHandle prepender = prepender(ptypes[pos]);
1598                         mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,



1599                                 1, 0, // indexCoder, storage
1600                                 2 + pos  // selected argument
1601                         );




1602                         break;
1603                     }
1604                     default:
1605                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1606                 }
1607             }
1608 


















1609             // Fold in byte[] instantiation at argument 0
1610             mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, NEW_ARRAY,
1611                     1 // index
1612             );
1613 
1614             // Start combining length and coder mixers.
1615             //
1616             // Length is easy: constant lengths can be computed on the spot, and all non-constant
1617             // shapes have been either converted to Strings, or explicit methods for getting the
1618             // string length out of primitives are provided.
1619             //
1620             // Coders are more interesting. Only Object, String and char arguments (and constants)
1621             // can have non-Latin1 encoding. It is easier to blindly convert constants to String,
1622             // and deduce the coder from there. Arguments would be either converted to Strings
1623             // during the initial filtering, or handled by specializations in MIXERS.
1624             //
1625             // The method handle shape before and after all mixers are combined in is:
1626             //   (long, <args>)String = ("indexCoder", <args>)
1627             long initialLengthCoder = INITIAL_CODER;
1628             for (RecipeElement el : recipe.getElements()) {
1629                 switch (el.getTag()) {
1630                     case TAG_CONST:
1631                         String constant = el.getValue();
1632                         initialLengthCoder = (long)mixer(String.class).invoke(initialLengthCoder, constant);
1633                         break;
1634                     case TAG_ARG:
1635                         int ac = el.getArgPos();
1636 
1637                         Class<?> argClass = ptypes[ac];
1638                         MethodHandle mix = mixer(argClass);
1639 
1640                         // Compute new "index" in-place using old value plus the appropriate argument.
1641                         mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix,
1642                                 0, // old-index
1643                                 1 + ac // selected argument
1644                         );
1645 
1646                         break;
1647                     default:
1648                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1649                 }
1650             }
1651 
1652             // Insert initial length and coder value here.
1653             // The method handle shape here is (<args>).
1654             mh = MethodHandles.insertArguments(mh, 0, initialLengthCoder);
1655 
1656             // Apply filters, converting the arguments:
1657             if (filters != null) {
1658                 mh = MethodHandles.filterArguments(mh, 0, filters);
1659             }
1660 
1661             return mh;
1662         }
1663 
1664         private static MethodHandle prepender(Class<?> cl) {
1665             return PREPENDERS.computeIfAbsent(cl, PREPEND);


1666         }
1667 
1668         private static MethodHandle mixer(Class<?> cl) {
1669             return MIXERS.computeIfAbsent(cl, MIX);
1670         }
1671 
1672         // This one is deliberately non-lambdified to optimize startup time:
1673         private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
1674             @Override
1675             public MethodHandle apply(Class<?> c) {
1676                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
1677                         Wrapper.asPrimitiveType(c));
1678             }
1679         };
1680 
1681         // This one is deliberately non-lambdified to optimize startup time:
1682         private static final Function<Class<?>, MethodHandle> MIX = new Function<Class<?>, MethodHandle>() {
1683             @Override
1684             public MethodHandle apply(Class<?> c) {
1685                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
1686                         Wrapper.asPrimitiveType(c));
1687             }
1688         };
1689 
1690         private static final MethodHandle SIMPLE;
1691         private static final MethodHandle NEW_STRING;
1692         private static final MethodHandle NEW_ARRAY;
1693         private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;
1694         private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
1695         private static final long INITIAL_CODER;
1696 
1697         static {
1698             try {
1699                 MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", long.class);
1700                 INITIAL_CODER = (long) initCoder.invoke();
1701             } catch (Throwable e) {
1702                 throw new AssertionError(e);




  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import jdk.internal.misc.Unsafe;
  29 import jdk.internal.misc.VM;
  30 import jdk.internal.org.objectweb.asm.ClassWriter;
  31 import jdk.internal.org.objectweb.asm.Label;
  32 import jdk.internal.org.objectweb.asm.MethodVisitor;
  33 import jdk.internal.org.objectweb.asm.Opcodes;

  34 import sun.invoke.util.Wrapper;

  35 
  36 import java.lang.invoke.MethodHandles.Lookup;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.List;
  40 import java.util.Objects;

  41 import java.util.concurrent.ConcurrentHashMap;
  42 import java.util.concurrent.ConcurrentMap;
  43 import java.util.function.Function;
  44 
  45 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  46 
  47 /**
  48  * <p>Methods to facilitate the creation of String concatenation methods, that
  49  * can be used to efficiently concatenate a known number of arguments of known
  50  * types, possibly after type adaptation and partial evaluation of arguments.
  51  * These methods are typically used as <em>bootstrap methods</em> for {@code
  52  * invokedynamic} call sites, to support the <em>string concatenation</em>
  53  * feature of the Java Programming Language.
  54  *
  55  * <p>Indirect access to the behavior specified by the provided {@code
  56  * MethodHandle} proceeds in order through two phases:
  57  *
  58  * <ol>
  59  *     <li><em>Linkage</em> occurs when the methods in this class are invoked.
  60  * They take as arguments a method type describing the concatenated arguments


1512      * the character length of the integers, and the private String constructor
1513      * that accepts byte[] arrays without copying. While this strategy assumes a
1514      * particular implementation details for String, this opens the door for
1515      * building a very optimal concatenation sequence. This is the only strategy
1516      * that requires porting if there are private JDK changes occur.
1517      */
1518     private static final class MethodHandleInlineCopyStrategy {
1519         static final Unsafe UNSAFE = Unsafe.getUnsafe();
1520 
1521         private MethodHandleInlineCopyStrategy() {
1522             // no instantiation
1523         }
1524 
1525         static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
1526 
1527             // Fast-path two-argument Object + Object concatenations
1528             if (recipe.getElements().size() == 2) {
1529                 // Two object arguments
1530                 if (mt.parameterCount() == 2 &&
1531                     !mt.parameterType(0).isPrimitive() &&
1532                     !mt.parameterType(1).isPrimitive() &&
1533                     recipe.getElements().get(0).getTag() == TAG_ARG &&
1534                     recipe.getElements().get(1).getTag() == TAG_ARG) {
1535 
1536                     return SIMPLE;
1537 
1538                 } else if (mt.parameterCount() == 1 &&
1539                            !mt.parameterType(0).isPrimitive()) {
1540                     // One Object argument, one constant
1541                     MethodHandle mh = SIMPLE;

1542 

1543                     if (recipe.getElements().get(0).getTag() == TAG_CONST &&
1544                         recipe.getElements().get(1).getTag() == TAG_ARG) {
1545                         // First recipe element is a constant
1546                         return MethodHandles.insertArguments(mh, 0,
1547                                 recipe.getElements().get(0).getValue());
1548 
1549                     } else if (recipe.getElements().get(1).getTag() == TAG_CONST &&
1550                                recipe.getElements().get(0).getTag() == TAG_ARG) {
1551                         // Second recipe element is a constant
1552                         return MethodHandles.insertArguments(mh, 1,
1553                                 recipe.getElements().get(1).getValue());
1554 
1555                     }

1556                 }
1557                 // else... fall-through to slow-path
1558             }
1559 
1560             // Create filters and obtain filtered parameter types. Filters would be used in the beginning
1561             // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
1562             // The filtered argument type list is used all over in the combinators below.
1563             Class<?>[] ptypes = mt.parameterArray();
1564             MethodHandle[] filters = null;
1565             for (int i = 0; i < ptypes.length; i++) {
1566                 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1567                 if (filter != null) {
1568                     if (filters == null) {
1569                         filters = new MethodHandle[ptypes.length];
1570                     }
1571                     filters[i] = filter;
1572                     ptypes[i] = filter.type().returnType();
1573                 }
1574             }
1575 
1576             // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1577             // with the (byte[], long)String shape to invoke newString in StringConcatHelper. The combinators are
1578             // assembled bottom-up, which makes the code arguably hard to read.
1579 
1580             // Drop all remaining parameter types, leave only helper arguments:
1581             MethodHandle mh;
1582 
1583             mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1584 
1585             long initialLengthCoder = INITIAL_CODER;
1586 
1587             // Mix in prependers. This happens when (byte[], long) = (storage, indexCoder) is already
1588             // known from the combinators below. We are assembling the string backwards, so the index coded
1589             // into indexCoder is the *ending* index.
1590 
1591             // We need one prepender per argument, but also need to fold in constants. We do so by greedily
1592             // create prependers that fold in surrounding constants into the argument prepender. This reduces
1593             // the number of unique MH combinator tree shapes we'll create in an application.
1594             String prefixConstant = null, suffixConstant = null;
1595             int pos = -1;
1596             for (RecipeElement el : recipe.getElements()) {
1597                 // Do the prepend, and put "new" index at index 1
1598                 switch (el.getTag()) {
1599                     case TAG_CONST: {
1600                         String constantValue = el.getValue();
1601 
1602                         // Eagerly update the initialLengthCoder value
1603                         initialLengthCoder = (long)mixer(String.class).invoke(initialLengthCoder, constantValue);
1604 
1605                         if (pos < 0) {
1606                             // Collecting into prefixConstant
1607                             prefixConstant = prefixConstant == null ? constantValue : prefixConstant + constantValue;
1608                         } else {
1609                             // Collecting into suffixConstant
1610                             suffixConstant = suffixConstant == null ? constantValue : suffixConstant + constantValue;
1611                         }
1612                         break;
1613                     }
1614                     case TAG_ARG: {
1615 
1616                         if (pos >= 0) {
1617                             // Flush the previous non-constant arg with any prefix/suffix constant
1618                             mh = MethodHandles.filterArgumentsWithCombiner(
1619                                 mh, 1,
1620                                 prepender(prefixConstant, ptypes[pos], suffixConstant),
1621                                 1, 0, // indexCoder, storage
1622                                 2 + pos  // selected argument
1623                             );
1624                             prefixConstant = suffixConstant = null;
1625                         }
1626                         // Mark the pos of next non-constant arg
1627                         pos = el.getArgPos();
1628                         break;
1629                     }
1630                     default:
1631                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1632                 }
1633             }
1634 
1635             // Insert any trailing args, constants
1636             if (pos >= 0) {
1637                 mh = MethodHandles.filterArgumentsWithCombiner(
1638                     mh, 1,
1639                     prepender(prefixConstant, ptypes[pos], suffixConstant),
1640                     1, 0, // indexCoder, storage
1641                     2 + pos  // selected argument
1642                 );
1643             } else if (prefixConstant != null) {
1644                 assert (suffixConstant == null);
1645                 // Sole prefixConstant can only happen if there were no non-constant arguments
1646                 mh = MethodHandles.filterArgumentsWithCombiner(
1647                     mh, 1,
1648                     MethodHandles.insertArguments(prepender(null, String.class, null), 2, prefixConstant),
1649                     1, 0 // indexCoder, storage
1650                 );
1651             }
1652 
1653             // Fold in byte[] instantiation at argument 0
1654             mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, NEW_ARRAY,
1655                     1 // index
1656             );
1657 
1658             // Start combining length and coder mixers.
1659             //
1660             // Length is easy: constant lengths can be computed on the spot, and all non-constant
1661             // shapes have been either converted to Strings, or explicit methods for getting the
1662             // string length out of primitives are provided.
1663             //
1664             // Coders are more interesting. Only Object, String and char arguments (and constants)
1665             // can have non-Latin1 encoding. It is easier to blindly convert constants to String,
1666             // and deduce the coder from there. Arguments would be either converted to Strings
1667             // during the initial filtering, or handled by specializations in MIXERS.
1668             //
1669             // The method handle shape before and after all mixers are combined in is:
1670             //   (long, <args>)String = ("indexCoder", <args>)
1671 
1672             for (RecipeElement el : recipe.getElements()) {
1673                 switch (el.getTag()) {
1674                     case TAG_CONST:
1675                         // Constants already handled in the code above

1676                         break;
1677                     case TAG_ARG:
1678                         int ac = el.getArgPos();
1679 
1680                         Class<?> argClass = ptypes[ac];
1681                         MethodHandle mix = mixer(argClass);
1682 
1683                         // Compute new "index" in-place using old value plus the appropriate argument.
1684                         mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix,
1685                                 0, // old-index
1686                                 1 + ac // selected argument
1687                         );
1688 
1689                         break;
1690                     default:
1691                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1692                 }
1693             }
1694 
1695             // Insert initial length and coder value here.
1696             // The method handle shape here is (<args>).
1697             mh = MethodHandles.insertArguments(mh, 0, initialLengthCoder);
1698 
1699             // Apply filters, converting the arguments:
1700             if (filters != null) {
1701                 mh = MethodHandles.filterArguments(mh, 0, filters);
1702             }
1703 
1704             return mh;
1705         }
1706 
1707         private static MethodHandle prepender(String prefix, Class<?> cl, String suffix) {
1708             return MethodHandles.insertArguments(
1709                     MethodHandles.insertArguments(
1710                         PREPENDERS.computeIfAbsent(cl, PREPEND),2, prefix), 3, suffix);
1711         }
1712 
1713         private static MethodHandle mixer(Class<?> cl) {
1714             return MIXERS.computeIfAbsent(cl, MIX);
1715         }
1716 
1717         // This one is deliberately non-lambdified to optimize startup time:
1718         private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
1719             @Override
1720             public MethodHandle apply(Class<?> c) {
1721                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
1722                         String.class, Wrapper.asPrimitiveType(c), String.class);
1723             }
1724         };
1725 
1726         // This one is deliberately non-lambdified to optimize startup time:
1727         private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
1728             @Override
1729             public MethodHandle apply(Class<?> c) {
1730                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
1731                         Wrapper.asPrimitiveType(c));
1732             }
1733         };
1734 
1735         private static final MethodHandle SIMPLE;
1736         private static final MethodHandle NEW_STRING;
1737         private static final MethodHandle NEW_ARRAY;
1738         private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;
1739         private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
1740         private static final long INITIAL_CODER;
1741 
1742         static {
1743             try {
1744                 MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", long.class);
1745                 INITIAL_CODER = (long) initCoder.invoke();
1746             } catch (Throwable e) {
1747                 throw new AssertionError(e);


< prev index next >