< prev index next >
src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
Print this page
rev 15320 : 8163370: Reduce number of classes loaded by common usage of java.lang.invoke
Reviewed-by: igerasim, psandoz
*** 279,290 ****
acc.setLength(0);
}
if (c == TAG_CONST) {
Object cnst = constants[constC++];
el.add(new RecipeElement(cnst));
! }
! if (c == TAG_ARG) {
el.add(new RecipeElement(argC++));
}
} else {
// Not a special characters, this is a constant embedded into
// the recipe itself.
--- 279,289 ----
acc.setLength(0);
}
if (c == TAG_CONST) {
Object cnst = constants[constC++];
el.add(new RecipeElement(cnst));
! } else if (c == TAG_ARG) {
el.add(new RecipeElement(argC++));
}
} else {
// Not a special characters, this is a constant embedded into
// the recipe itself.
*** 320,378 ****
}
private static final class RecipeElement {
private final Object value;
private final int argPos;
- private final Tag tag;
public RecipeElement(Object cnst) {
this.value = Objects.requireNonNull(cnst);
this.argPos = -1;
- this.tag = Tag.CONST;
}
public RecipeElement(int arg) {
this.value = null;
this.argPos = arg;
- this.tag = Tag.ARG;
}
public Object getValue() {
! assert (tag == Tag.CONST);
return value;
}
public int getArgPos() {
! assert (tag == Tag.ARG);
return argPos;
}
! public Tag getTag() {
! return tag;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecipeElement that = (RecipeElement) o;
! if (tag != that.tag) return false;
! if (tag == Tag.CONST && (!value.equals(that.value))) return false;
! if (tag == Tag.ARG && (argPos != that.argPos)) return false;
return true;
}
@Override
public int hashCode() {
! return tag.hashCode();
! }
}
-
- private enum Tag {
- CONST, ARG
}
/**
* Facilitates the creation of optimized String concatenation methods, that
* can be used to efficiently concatenate a known number of arguments of
--- 319,372 ----
}
private static final class RecipeElement {
private final Object value;
private final int argPos;
public RecipeElement(Object cnst) {
this.value = Objects.requireNonNull(cnst);
this.argPos = -1;
}
public RecipeElement(int arg) {
this.value = null;
+ assert (arg >= 0);
this.argPos = arg;
}
public Object getValue() {
! assert (isConst());
return value;
}
public int getArgPos() {
! assert (!isConst());
return argPos;
}
! public boolean isConst() {
! return argPos == -1;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecipeElement that = (RecipeElement) o;
! boolean isConst = isConst();
! if (isConst != that.isConst()) return false;
! if (isConst && (!value.equals(that.value))) return false;
! if (!isConst && (argPos != that.argPos)) return false;
return true;
}
@Override
public int hashCode() {
! return argPos;
}
}
/**
* Facilitates the creation of optimized String concatenation methods, that
* can be used to efficiently concatenate a known number of arguments of
*** 878,893 ****
need to do null-checks early, not make the append chain shape simpler.
*/
int off = 0;
for (RecipeElement el : recipe.getElements()) {
! switch (el.getTag()) {
! case CONST: {
// Guaranteed non-null, no null check required.
! break;
! }
! case ARG: {
// Null-checks are needed only for String arguments, and when a previous stage
// did not do implicit null-checks. If a String is null, we eagerly replace it
// with "null" constant. Note, we omit Objects here, because we don't call
// .length() on them down below.
int ac = el.getArgPos();
--- 872,884 ----
need to do null-checks early, not make the append chain shape simpler.
*/
int off = 0;
for (RecipeElement el : recipe.getElements()) {
! if (el.isConst()) {
// Guaranteed non-null, no null check required.
! } else {
// Null-checks are needed only for String arguments, and when a previous stage
// did not do implicit null-checks. If a String is null, we eagerly replace it
// with "null" constant. Note, we omit Objects here, because we don't call
// .length() on them down below.
int ac = el.getArgPos();
*** 899,912 ****
mv.visitLdcInsn("null");
mv.visitIntInsn(ASTORE, off);
mv.visitLabel(l0);
}
off += getParameterSize(cl);
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
}
// Prepare StringBuilder instance
--- 890,899 ----
*** 923,939 ****
int off = 0;
mv.visitInsn(ICONST_0);
for (RecipeElement el : recipe.getElements()) {
! switch (el.getTag()) {
! case CONST: {
Object cnst = el.getValue();
len += cnst.toString().length();
! break;
! }
! case ARG: {
/*
If an argument is String, then we can call .length() on it. Sized/Exact modes have
converted arguments for us. If an argument is primitive, we can provide a guess
for its String representation size.
*/
--- 910,923 ----
int off = 0;
mv.visitInsn(ICONST_0);
for (RecipeElement el : recipe.getElements()) {
! if (el.isConst()) {
Object cnst = el.getValue();
len += cnst.toString().length();
! } else {
/*
If an argument is String, then we can call .length() on it. Sized/Exact modes have
converted arguments for us. If an argument is primitive, we can provide a guess
for its String representation size.
*/
*** 950,963 ****
mv.visitInsn(IADD);
} else if (cl.isPrimitive()) {
len += estimateSize(cl);
}
off += getParameterSize(cl);
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
// Constants have non-zero length, mix in
if (len > 0) {
--- 934,943 ----
*** 985,1011 ****
// At this point, we have a blank StringBuilder on stack, fill it in with .append calls.
{
int off = 0;
for (RecipeElement el : recipe.getElements()) {
String desc;
! switch (el.getTag()) {
! case CONST: {
Object cnst = el.getValue();
mv.visitLdcInsn(cnst);
desc = getSBAppendDesc(cnst.getClass());
! break;
! }
! case ARG: {
Class<?> cl = arr[el.getArgPos()];
mv.visitVarInsn(getLoadOpcode(cl), off);
off += getParameterSize(cl);
desc = getSBAppendDesc(cl);
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
desc,
--- 965,985 ----
// At this point, we have a blank StringBuilder on stack, fill it in with .append calls.
{
int off = 0;
for (RecipeElement el : recipe.getElements()) {
String desc;
! if (el.isConst()) {
Object cnst = el.getValue();
mv.visitLdcInsn(cnst);
desc = getSBAppendDesc(cnst.getClass());
! } else {
Class<?> cl = arr[el.getArgPos()];
mv.visitVarInsn(getLoadOpcode(cl), off);
off += getParameterSize(cl);
desc = getSBAppendDesc(cl);
}
+
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
desc,
*** 1277,1306 ****
// Figure out lengths: constants' lengths can be deduced on the spot.
// All reference arguments were filtered to String in the combinators below, so we can
// call the usual String.length(). Primitive values string sizes can be estimated.
int initial = 0;
for (RecipeElement el : recipe.getElements()) {
! switch (el.getTag()) {
! case CONST: {
Object cnst = el.getValue();
initial += cnst.toString().length();
! break;
! }
! case ARG: {
final int i = el.getArgPos();
Class<?> type = ptypesList.get(i);
if (type.isPrimitive()) {
MethodHandle est = MethodHandles.constant(int.class, estimateSize(type));
est = MethodHandles.dropArguments(est, 0, type);
lengthers[i] = est;
} else {
lengthers[i] = STRING_LENGTH;
}
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
// Create (StringBuilder, <args>) shape for appending:
MethodHandle builder = MethodHandles.dropArguments(MethodHandles.identity(StringBuilder.class), 1, ptypesList);
--- 1251,1273 ----
// Figure out lengths: constants' lengths can be deduced on the spot.
// All reference arguments were filtered to String in the combinators below, so we can
// call the usual String.length(). Primitive values string sizes can be estimated.
int initial = 0;
for (RecipeElement el : recipe.getElements()) {
! if (el.isConst()) {
Object cnst = el.getValue();
initial += cnst.toString().length();
! } else {
final int i = el.getArgPos();
Class<?> type = ptypesList.get(i);
if (type.isPrimitive()) {
MethodHandle est = MethodHandles.constant(int.class, estimateSize(type));
est = MethodHandles.dropArguments(est, 0, type);
lengthers[i] = est;
} else {
lengthers[i] = STRING_LENGTH;
}
}
}
// Create (StringBuilder, <args>) shape for appending:
MethodHandle builder = MethodHandles.dropArguments(MethodHandles.identity(StringBuilder.class), 1, ptypesList);
*** 1309,1338 ****
// reverse as well.
List<RecipeElement> elements = recipe.getElements();
for (int i = elements.size() - 1; i >= 0; i--) {
RecipeElement el = elements.get(i);
MethodHandle appender;
! switch (el.getTag()) {
! case CONST: {
Object constant = el.getValue();
MethodHandle mh = appender(adaptToStringBuilder(constant.getClass()));
appender = MethodHandles.insertArguments(mh, 1, constant);
! break;
! }
! case ARG: {
int ac = el.getArgPos();
appender = appender(ptypesList.get(ac));
// Insert dummy arguments to match the prefix in the signature.
// The actual appender argument will be the ac-ith argument.
if (ac != 0) {
appender = MethodHandles.dropArguments(appender, 1, ptypesList.subList(0, ac));
}
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
builder = MethodHandles.foldArguments(builder, appender);
}
// Build the sub-tree that adds the sizes and produces a StringBuilder:
--- 1276,1298 ----
// reverse as well.
List<RecipeElement> elements = recipe.getElements();
for (int i = elements.size() - 1; i >= 0; i--) {
RecipeElement el = elements.get(i);
MethodHandle appender;
! if (el.isConst()) {
Object constant = el.getValue();
MethodHandle mh = appender(adaptToStringBuilder(constant.getClass()));
appender = MethodHandles.insertArguments(mh, 1, constant);
! } else {
int ac = el.getArgPos();
appender = appender(ptypesList.get(ac));
// Insert dummy arguments to match the prefix in the signature.
// The actual appender argument will be the ac-ith argument.
if (ac != 0) {
appender = MethodHandles.dropArguments(appender, 1, ptypesList.subList(0, ac));
}
}
builder = MethodHandles.foldArguments(builder, appender);
}
// Build the sub-tree that adds the sizes and produces a StringBuilder:
*** 1519,1541 ****
// Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already
// known from the combinators below. We are assembling the string backwards, so "index" is the
// *ending* index.
for (RecipeElement el : recipe.getElements()) {
MethodHandle prepender;
! switch (el.getTag()) {
! case CONST: {
Object cnst = el.getValue();
prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
! break;
! }
! case ARG: {
int pos = el.getArgPos();
prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos);
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
// Remove "old" index from arguments
mh = MethodHandles.dropArguments(mh, 1, int.class);
--- 1479,1494 ----
// Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already
// known from the combinators below. We are assembling the string backwards, so "index" is the
// *ending* index.
for (RecipeElement el : recipe.getElements()) {
MethodHandle prepender;
! if (el.isConst()) {
Object cnst = el.getValue();
prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
! } else {
int pos = el.getArgPos();
prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos);
}
// Remove "old" index from arguments
mh = MethodHandles.dropArguments(mh, 1, int.class);
*** 1571,1589 ****
// The method handle shape after all length and coder mixers is:
// (int, byte, <args>)String = ("index", "coder", <args>)
byte initialCoder = INITIAL_CODER;
int initialLen = 0; // initial length, in characters
for (RecipeElement el : recipe.getElements()) {
! switch (el.getTag()) {
! case CONST: {
Object constant = el.getValue();
String s = constant.toString();
initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s);
initialLen += s.length();
! break;
! }
! case ARG: {
int ac = el.getArgPos();
Class<?> argClass = ptypesList.get(ac);
MethodHandle lm = selectArgument(lengthMixer(argClass), 1, ptypesList, ac);
lm = MethodHandles.dropArguments(lm, 0, byte.class); // (*)
--- 1524,1539 ----
// The method handle shape after all length and coder mixers is:
// (int, byte, <args>)String = ("index", "coder", <args>)
byte initialCoder = INITIAL_CODER;
int initialLen = 0; // initial length, in characters
for (RecipeElement el : recipe.getElements()) {
! if (el.isConst()) {
Object constant = el.getValue();
String s = constant.toString();
initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s);
initialLen += s.length();
! } else {
int ac = el.getArgPos();
Class<?> argClass = ptypesList.get(ac);
MethodHandle lm = selectArgument(lengthMixer(argClass), 1, ptypesList, ac);
lm = MethodHandles.dropArguments(lm, 0, byte.class); // (*)
*** 1604,1617 ****
// 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>)
// Coder mixer ignores the "old-index" arg due to dropArguments above (**)
mh = MethodHandles.foldArguments(mh, cm);
// 1. The mh shape here is ("old-index", "old-coder", <args>)
- break;
- }
- default:
- throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
// Insert initial lengths and coders here.
// The method handle shape here is (<args>).
--- 1554,1563 ----
< prev index next >