546
547 return doStringConcat(lookup, name, concatType, false, recipe, constants);
548 }
549
550 private static CallSite doStringConcat(MethodHandles.Lookup lookup,
551 String name,
552 MethodType concatType,
553 boolean generateRecipe,
554 String recipe,
555 Object... constants) throws StringConcatException {
556 Objects.requireNonNull(lookup, "Lookup is null");
557 Objects.requireNonNull(name, "Name is null");
558 Objects.requireNonNull(concatType, "Concat type is null");
559 Objects.requireNonNull(constants, "Constants are null");
560
561 for (Object o : constants) {
562 Objects.requireNonNull(o, "Cannot accept null constants");
563 }
564
565 if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) {
566 throw new StringConcatException(String.format(
567 "Invalid caller: %s",
568 lookup.lookupClass().getName()));
569 }
570
571 int cCount = 0;
572 int oCount = 0;
573 if (generateRecipe) {
574 // Mock the recipe to reuse the concat generator code
575 char[] value = new char[concatType.parameterCount()];
576 Arrays.fill(value, TAG_ARG);
577 recipe = new String(value);
578 oCount = concatType.parameterCount();
579 } else {
580 Objects.requireNonNull(recipe, "Recipe is null");
581
582 for (int i = 0; i < recipe.length(); i++) {
583 char c = recipe.charAt(i);
584 if (c == TAG_CONST) cCount++;
585 if (c == TAG_ARG) oCount++;
586 }
587 }
588
1478 MethodHandle[] filters = null;
1479 for (int i = 0; i < ptypes.length; i++) {
1480 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1481 if (filter != null) {
1482 if (filters == null) {
1483 filters = new MethodHandle[ptypes.length];
1484 }
1485 filters[i] = filter;
1486 ptypes[i] = filter.type().returnType();
1487 }
1488 }
1489
1490 // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1491 // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,
1492 // which makes the code arguably hard to read.
1493
1494 // Drop all remaining parameter types, leave only helper arguments:
1495 MethodHandle mh;
1496
1497 mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1498 mh = MethodHandles.dropArguments(mh, 0, int.class);
1499
1500 // Safety: check that remaining index is zero -- that would mean the storage is completely
1501 // overwritten, and no leakage of uninitialized data occurred.
1502 mh = MethodHandles.filterArgument(mh, 0, CHECK_INDEX);
1503
1504 // Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already
1505 // known from the combinators below. We are assembling the string backwards, so "index" is the
1506 // *ending* index.
1507 for (RecipeElement el : recipe.getElements()) {
1508 MethodHandle prepender;
1509 switch (el.getTag()) {
1510 case TAG_CONST:
1511 Object cnst = el.getValue();
1512 prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
1513 break;
1514 case TAG_ARG:
1515 int pos = el.getArgPos();
1516 prepender = selectArgument(prepender(ptypes[pos]), 3, ptypes, pos);
1517 break;
1518 default:
1519 throw new StringConcatException("Unhandled tag: " + el.getTag());
1520 }
1521
1522 // Remove "old" index from arguments
1523 mh = MethodHandles.dropArguments(mh, 1, int.class);
1524
1525 // Do the prepend, and put "new" index at index 0
1526 mh = MethodHandles.foldArguments(mh, prepender);
1527 }
1528
1529 // Prepare the argument list for prepending. The tree below would instantiate
1530 // the storage byte[] into argument 0, so we need to swap "storage" and "index".
1531 // The index at this point equals to "size", and resides at argument 1.
1532 {
1533 MethodType nmt = mh.type()
1534 .changeParameterType(0, byte[].class)
1535 .changeParameterType(1, int.class);
1536 mh = MethodHandles.permuteArguments(mh, nmt, swap10(nmt.parameterCount()));
1537 }
1538
1539 // Fold in byte[] instantiation at argument 0.
1540 MethodHandle combiner = MethodHandles.dropArguments(NEW_ARRAY, 2, ptypes);
1541 mh = MethodHandles.foldArguments(mh, combiner);
1542
1543 // Start combining length and coder mixers.
1544 //
1545 // Length is easy: constant lengths can be computed on the spot, and all non-constant
1546 // shapes have been either converted to Strings, or explicit methods for getting the
1547 // string length out of primitives are provided.
1548 //
1549 // Coders are more interesting. Only Object, String and char arguments (and constants)
1550 // can have non-Latin1 encoding. It is easier to blindly convert constants to String,
1551 // and deduce the coder from there. Arguments would be either converted to Strings
1552 // during the initial filtering, or handled by primitive specializations in CODER_MIXERS.
1553 //
1554 // The method handle shape after all length and coder mixers is:
1555 // (int, byte, <args>)String = ("index", "coder", <args>)
1556 byte initialCoder = INITIAL_CODER;
1557 int initialLen = 0; // initial length, in characters
1558 for (RecipeElement el : recipe.getElements()) {
1559 switch (el.getTag()) {
1560 case TAG_CONST:
1561 Object constant = el.getValue();
1562 String s = constant.toString();
1563 initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s);
1564 initialLen += s.length();
1565 break;
1566 case TAG_ARG:
1567 int ac = el.getArgPos();
1568
1569 Class<?> argClass = ptypes[ac];
1570 MethodHandle lm = selectArgument(lengthMixer(argClass), 1, ptypes, ac);
1571 lm = MethodHandles.dropArguments(lm, 0, byte.class); // (*)
1572 lm = MethodHandles.dropArguments(lm, 2, byte.class);
1573
1574 MethodHandle cm = selectArgument(coderMixer(argClass), 1, ptypes, ac);
1575 cm = MethodHandles.dropArguments(cm, 0, int.class); // (**)
1576
1577 // Read this bottom up:
1578
1579 // 4. Drop old index and coder, producing ("new-index", "new-coder", <args>)
1580 mh = MethodHandles.dropArguments(mh, 2, int.class, byte.class);
1581
1582 // 3. Compute "new-index", producing ("new-index", "new-coder", "old-index", "old-coder", <args>)
1583 // Length mixer ignores both "new-coder" and "old-coder" due to dropArguments above (*)
1584 mh = MethodHandles.foldArguments(mh, lm);
1585
1586 // 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>)
1587 // Coder mixer ignores the "old-index" arg due to dropArguments above (**)
1588 mh = MethodHandles.foldArguments(mh, cm);
1589
1590 // 1. The mh shape here is ("old-index", "old-coder", <args>)
1591 break;
1592 default:
1593 throw new StringConcatException("Unhandled tag: " + el.getTag());
1594 }
1595 }
1596
1597 // Insert initial lengths and coders here.
1598 // The method handle shape here is (<args>).
1599 mh = MethodHandles.insertArguments(mh, 0, initialLen, initialCoder);
1600
1601 // Apply filters, converting the arguments:
1602 if (filters != null) {
1603 mh = MethodHandles.filterArguments(mh, 0, filters);
1604 }
1605
1606 return mh;
1607 }
1608
1609 private static int[] swap10(int count) {
1610 int[] perm = new int[count];
1611 perm[0] = 1;
1612 perm[1] = 0;
1613 for (int i = 2; i < count; i++) {
1614 perm[i] = i;
1615 }
1616 return perm;
1617 }
1618
1619 // Adapts: (...prefix..., parameter[pos])R -> (...prefix..., ...parameters...)R
1620 private static MethodHandle selectArgument(MethodHandle mh, int prefix, Class<?>[] ptypes, int pos) {
1621 if (pos == 0) {
1622 return MethodHandles.dropArguments(mh, prefix + 1, Arrays.copyOfRange(ptypes, 1, ptypes.length));
1623 } else if (pos == ptypes.length - 1) {
1624 return MethodHandles.dropArguments(mh, prefix, Arrays.copyOf(ptypes, ptypes.length - 1));
1625 } else { // 0 < pos < ptypes.size() - 1
1626 MethodHandle t = MethodHandles.dropArguments(mh, prefix, Arrays.copyOf(ptypes, pos));
1627 return MethodHandles.dropArguments(t, prefix + 1 + pos, Arrays.copyOfRange(ptypes, pos + 1, ptypes.length));
1628 }
1629 }
1630
1631 @ForceInline
1632 private static byte[] newArray(int length, byte coder) {
1633 return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
1634 }
1635
1636 @ForceInline
1637 private static int checkIndex(int index) {
1638 if (index != 0) {
1639 throw new IllegalStateException("Storage is not completely initialized, " + index + " bytes left");
1640 }
1641 return index;
1642 }
1643
1644 private static MethodHandle prepender(Class<?> cl) {
1645 return PREPENDERS.computeIfAbsent(cl, PREPEND);
1646 }
1647
1648 private static MethodHandle coderMixer(Class<?> cl) {
|
546
547 return doStringConcat(lookup, name, concatType, false, recipe, constants);
548 }
549
550 private static CallSite doStringConcat(MethodHandles.Lookup lookup,
551 String name,
552 MethodType concatType,
553 boolean generateRecipe,
554 String recipe,
555 Object... constants) throws StringConcatException {
556 Objects.requireNonNull(lookup, "Lookup is null");
557 Objects.requireNonNull(name, "Name is null");
558 Objects.requireNonNull(concatType, "Concat type is null");
559 Objects.requireNonNull(constants, "Constants are null");
560
561 for (Object o : constants) {
562 Objects.requireNonNull(o, "Cannot accept null constants");
563 }
564
565 if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) {
566 throw new StringConcatException("Invalid caller: " +
567 lookup.lookupClass().getName());
568 }
569
570 int cCount = 0;
571 int oCount = 0;
572 if (generateRecipe) {
573 // Mock the recipe to reuse the concat generator code
574 char[] value = new char[concatType.parameterCount()];
575 Arrays.fill(value, TAG_ARG);
576 recipe = new String(value);
577 oCount = concatType.parameterCount();
578 } else {
579 Objects.requireNonNull(recipe, "Recipe is null");
580
581 for (int i = 0; i < recipe.length(); i++) {
582 char c = recipe.charAt(i);
583 if (c == TAG_CONST) cCount++;
584 if (c == TAG_ARG) oCount++;
585 }
586 }
587
1477 MethodHandle[] filters = null;
1478 for (int i = 0; i < ptypes.length; i++) {
1479 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1480 if (filter != null) {
1481 if (filters == null) {
1482 filters = new MethodHandle[ptypes.length];
1483 }
1484 filters[i] = filter;
1485 ptypes[i] = filter.type().returnType();
1486 }
1487 }
1488
1489 // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1490 // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,
1491 // which makes the code arguably hard to read.
1492
1493 // Drop all remaining parameter types, leave only helper arguments:
1494 MethodHandle mh;
1495
1496 mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1497 mh = MethodHandles.dropArguments(mh, 1, int.class);
1498
1499 // Safety: check that remaining index is zero -- that would mean the storage is completely
1500 // overwritten, and no leakage of uninitialized data occurred.
1501 mh = MethodHandles.filterArgument(mh, 1, CHECK_INDEX);
1502
1503 // Mix in prependers. This happens when (byte[], int, byte) = (storage, index, coder) is already
1504 // known from the combinators below. We are assembling the string backwards, so "index" is the
1505 // *ending* index.
1506 for (RecipeElement el : recipe.getElements()) {
1507 // Do the prepend, and put "new" index at index 1
1508 mh = MethodHandles.dropArguments(mh, 2, int.class);
1509 switch (el.getTag()) {
1510 case TAG_CONST: {
1511 Object cnst = el.getValue();
1512 MethodHandle prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
1513 mh = MethodHandles.foldArguments(mh, 1, prepender,
1514 2, 0, 3 // index, storage, coder
1515 );
1516 break;
1517 }
1518 case TAG_ARG: {
1519 int pos = el.getArgPos();
1520 MethodHandle prepender = prepender(ptypes[pos]);
1521 mh = MethodHandles.foldArguments(mh, 1, prepender,
1522 2, 0, 3, // index, storage, coder
1523 4 + pos // selected argument
1524 );
1525 break;
1526 }
1527 default:
1528 throw new StringConcatException("Unhandled tag: " + el.getTag());
1529 }
1530 }
1531
1532 // Fold in byte[] instantiation at argument 0
1533 mh = MethodHandles.foldArguments(mh, 0, NEW_ARRAY,
1534 1, 2 // index, coder
1535 );
1536
1537 // Start combining length and coder mixers.
1538 //
1539 // Length is easy: constant lengths can be computed on the spot, and all non-constant
1540 // shapes have been either converted to Strings, or explicit methods for getting the
1541 // string length out of primitives are provided.
1542 //
1543 // Coders are more interesting. Only Object, String and char arguments (and constants)
1544 // can have non-Latin1 encoding. It is easier to blindly convert constants to String,
1545 // and deduce the coder from there. Arguments would be either converted to Strings
1546 // during the initial filtering, or handled by primitive specializations in CODER_MIXERS.
1547 //
1548 // The method handle shape after all length and coder mixers is:
1549 // (int, byte, <args>)String = ("index", "coder", <args>)
1550 byte initialCoder = INITIAL_CODER;
1551 int initialLen = 0; // initial length, in characters
1552 for (RecipeElement el : recipe.getElements()) {
1553 switch (el.getTag()) {
1554 case TAG_CONST:
1555 Object constant = el.getValue();
1556 String s = constant.toString();
1557 initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s);
1558 initialLen += s.length();
1559 break;
1560 case TAG_ARG:
1561 int ac = el.getArgPos();
1562
1563 Class<?> argClass = ptypes[ac];
1564 MethodHandle lm = lengthMixer(argClass);
1565 MethodHandle cm = coderMixer(argClass);
1566
1567 // Read this bottom up:
1568
1569 // 4. Drop old index and coder, producing ("new-index", "new-coder", <args>)
1570 mh = MethodHandles.dropArguments(mh, 2, int.class, byte.class);
1571
1572 // 3. Compute "new-index", producing ("new-index", "new-coder", "old-index", "old-coder", <args>)
1573 // Length mixer needs old index, plus the appropriate argument
1574 mh = MethodHandles.foldArguments(mh, 0, lm,
1575 2, // old-index
1576 4 + ac // selected argument
1577 );
1578
1579 // 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>)
1580 // Coder mixer needs old coder, plus the appropriate argument.
1581 mh = MethodHandles.foldArguments(mh, 0, cm,
1582 2, // old-coder
1583 3 + ac // selected argument
1584 );
1585
1586 // 1. The mh shape here is ("old-index", "old-coder", <args>)
1587 break;
1588 default:
1589 throw new StringConcatException("Unhandled tag: " + el.getTag());
1590 }
1591 }
1592
1593 // Insert initial lengths and coders here.
1594 // The method handle shape here is (<args>).
1595 mh = MethodHandles.insertArguments(mh, 0, initialLen, initialCoder);
1596
1597 // Apply filters, converting the arguments:
1598 if (filters != null) {
1599 mh = MethodHandles.filterArguments(mh, 0, filters);
1600 }
1601
1602 return mh;
1603 }
1604
1605 @ForceInline
1606 private static byte[] newArray(int length, byte coder) {
1607 return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
1608 }
1609
1610 @ForceInline
1611 private static int checkIndex(int index) {
1612 if (index != 0) {
1613 throw new IllegalStateException("Storage is not completely initialized, " + index + " bytes left");
1614 }
1615 return index;
1616 }
1617
1618 private static MethodHandle prepender(Class<?> cl) {
1619 return PREPENDERS.computeIfAbsent(cl, PREPEND);
1620 }
1621
1622 private static MethodHandle coderMixer(Class<?> cl) {
|