Skip to content

Commit

Permalink
Provide a collector for indy varargs boxing
Browse files Browse the repository at this point in the history
It turns out that MethodHandle.asCollector is very slow for
subclasses of Object[] due to double-allocation and double-copying
done by the OpenJDK logic. To avoid this overhead, we use 10-wide
overloads of our own collector utility functions and pass to
Binder.collect, which now supports the
MethodHandles.collectArguments decorator.

Fixes #6628
  • Loading branch information
headius committed Mar 26, 2021
1 parent 1796633 commit fa48c66
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 9 deletions.
2 changes: 1 addition & 1 deletion core/pom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
jar 'org.jruby.jcodings:jcodings:1.0.55'
jar 'org.jruby:dirgra:0.3'

jar 'com.headius:invokebinder:1.11'
jar 'com.headius:invokebinder:1.12'
jar 'com.headius:options:1.5'

jar 'com.jcraft:jzlib:1.1.3'
Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ DO NOT MODIFIY - GENERATED CODE
<dependency>
<groupId>com.headius</groupId>
<artifactId>invokebinder</artifactId>
<version>1.11</version>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.headius</groupId>
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/jruby/RubyString.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public class RubyString extends RubyObject implements CharSequence, EncodingCapa
private static final byte[] SCRUB_REPL_UTF32LE = new byte[]{(byte)0xFD, (byte)0xFF, (byte)0x00, (byte)0x00};
private static final byte[] FORCE_ENCODING_BYTES = ".force_encoding(\"".getBytes();

public static RubyString[] NULL_ARRAY = {};

private volatile int shareLevel = SHARE_LEVEL_NONE;

private ByteList value;
Expand Down
11 changes: 6 additions & 5 deletions core/src/main/java/org/jruby/ir/targets/indy/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.methodType;
import static org.jruby.runtime.Helpers.arrayOf;
import static org.jruby.runtime.Helpers.constructObjectArrayHandle;
import static org.jruby.runtime.invokedynamic.InvokeDynamicSupport.findStatic;
import static org.jruby.runtime.invokedynamic.InvokeDynamicSupport.findVirtual;
import static org.jruby.util.CodegenUtils.p;
Expand Down Expand Up @@ -597,7 +598,7 @@ static MethodHandle buildIndyHandle(InvokeSite site, CacheEntry entry) {
if (!blockGiven) mh = insertArguments(mh, mh.type().parameterCount() - 1, Block.NULL_BLOCK);

mh = SmartBinder.from(lookup(), siteToDyncall)
.collect("args", "arg.*")
.collect("args", "arg.*", Helpers.constructObjectArrayHandle(site.arity))
.invoke(mh)
.handle();
}
Expand Down Expand Up @@ -636,7 +637,7 @@ static MethodHandle buildGenericHandle(InvokeSite site, CacheEntry entry) {
.insert(0, "method", DynamicMethod.class, method);

if (site.arity > 3) {
binder = binder.collect("args", "arg.*");
binder = binder.collect("args", "arg.*", constructObjectArrayHandle(site.arity));
}

if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) {
Expand All @@ -660,7 +661,7 @@ static MethodHandle buildMethodMissingHandle(InvokeSite site, CacheEntry entry,
site.name(),
self.getRuntime().newSymbol(site.methodName))
.insert(0, "method", DynamicMethod.class, method)
.collect("args", "arg.*");
.collect("args", "arg.*", Helpers.constructObjectArrayHandle(site.arity));
} else {
SmartHandle fold = SmartBinder.from(
site.signature
Expand Down Expand Up @@ -837,7 +838,7 @@ static MethodHandle buildJittedHandle(InvokeSite site, CacheEntry entry, boolean
mh = specific;
} else {
mh = (MethodHandle) compiledIRMethod.getHandle();
binder = binder.collect("args", "arg.*");
binder = binder.collect("args", "arg.*", Helpers.constructObjectArrayHandle(site.arity));
}
}

Expand Down Expand Up @@ -898,7 +899,7 @@ static MethodHandle buildNativeHandle(InvokeSite site, CacheEntry entry, boolean
} else {
// 1 or more args, collect into []
binder = SmartBinder.from(lookup(), site.signature)
.collect("args", "arg.*");
.collect("args", "arg.*", Helpers.constructObjectArrayHandle(site.arity));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.headius.invokebinder.SmartBinder;
import org.jruby.RubyRegexp;
import org.jruby.RubyString;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.util.RegexpOptions;
import org.objectweb.asm.Handle;
Expand Down Expand Up @@ -60,7 +61,7 @@ public Binder prepareBinder() {
// "once" deregexp must be handled on the call side
return SmartBinder
.from(RubyRegexp.class, argNames, argTypes)
.collect("parts", "part.*")
.collect("parts", "part.*", Helpers.constructRubyStringArrayHandle(argNames.length - 1))
.binder();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ public Binder prepareBinder(boolean varargs) {
binder = binder.insert(argOffset, "args", IRubyObject.NULL_ARRAY);
} else {
binder = binder
.collect("args", "arg[0-9]+");
.collect("args", "arg[0-9]+", Helpers.constructObjectArrayHandle(arity));
}
}

Expand Down
108 changes: 108 additions & 0 deletions core/src/main/java/org/jruby/runtime/Helpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;

import java.net.BindException;
Expand Down Expand Up @@ -32,6 +35,7 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.headius.invokebinder.Binder;
import jnr.constants.platform.Errno;
import org.jruby.*;
import org.jruby.ast.ArgsNode;
Expand Down Expand Up @@ -64,6 +68,7 @@
import org.jruby.runtime.invokedynamic.MethodNames;
import org.jruby.util.ArraySupport;
import org.jruby.util.ByteList;
import org.jruby.util.CodegenUtils;
import org.jruby.util.MurmurHash;
import org.jruby.util.TypeConverter;

Expand All @@ -78,6 +83,7 @@
import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.runtime.Visibility.PROTECTED;
import static org.jruby.runtime.invokedynamic.MethodNames.EQL;
import static org.jruby.util.CodegenUtils.params;
import static org.jruby.util.CodegenUtils.sig;
import static org.jruby.util.RubyStringBuilder.str;
import static org.jruby.util.RubyStringBuilder.ids;
Expand All @@ -95,6 +101,8 @@ public class Helpers {

public static final Pattern SEMICOLON_PATTERN = Pattern.compile(";");

private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

public static RubyClass getSingletonClass(Ruby runtime, IRubyObject receiver) {
if (receiver instanceof RubyFixnum || receiver instanceof RubySymbol) {
throw runtime.newTypeError("can't define singleton");
Expand Down Expand Up @@ -1299,6 +1307,106 @@ public static IRubyObject[] constructObjectArray(IRubyObject one, IRubyObject tw
return new IRubyObject[] {one, two, three, four, five, six, seven, eight, nine, ten};
}

private static final MethodHandle[] constructObjectArrayHandles = new MethodHandle[11];

public static MethodHandle constructObjectArrayHandle(int size) {
if (size < 0) throw new IllegalArgumentException("illegal size: " + size);

if (size > 10) {
return Binder
.from(IRubyObject[].class, params(IRubyObject.class, size))
.collect(0, IRubyObject[].class).identity();
}

MethodHandle handle = constructObjectArrayHandles[size];

if (handle == null) {
try {
if (size == 0) {
handle = Binder.from(IRubyObject[].class).getStatic(LOOKUP, IRubyObject.class, "NULL_ARRAY");
} else {
handle = MethodHandles.publicLookup().findStatic(Helpers.class, "constructObjectArray", MethodType.methodType(IRubyObject[].class, CodegenUtils.params(IRubyObject.class, size)));
}

constructObjectArrayHandles[size] = handle;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

return handle;
}

public static RubyString[] constructRubyStringArray(RubyString one) {
return new RubyString[] {one};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two) {
return new RubyString[] {one, two};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three) {
return new RubyString[] {one, two, three};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four) {
return new RubyString[] {one, two, three, four};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four, RubyString five) {
return new RubyString[] {one, two, three, four, five};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four, RubyString five, RubyString six) {
return new RubyString[] {one, two, three, four, five, six};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four, RubyString five, RubyString six, RubyString seven) {
return new RubyString[] {one, two, three, four, five, six, seven};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four, RubyString five, RubyString six, RubyString seven, RubyString eight) {
return new RubyString[] {one, two, three, four, five, six, seven, eight};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four, RubyString five, RubyString six, RubyString seven, RubyString eight, RubyString nine) {
return new RubyString[] {one, two, three, four, five, six, seven, eight, nine};
}

public static RubyString[] constructRubyStringArray(RubyString one, RubyString two, RubyString three, RubyString four, RubyString five, RubyString six, RubyString seven, RubyString eight, RubyString nine, RubyString ten) {
return new RubyString[] {one, two, three, four, five, six, seven, eight, nine, ten};
}

private static final MethodHandle[] constructRubyStringArrayHandles = new MethodHandle[11];

public static MethodHandle constructRubyStringArrayHandle(int size) {
if (size < 0) throw new IllegalArgumentException("illegal size: " + size);

if (size > 10) {
return Binder
.from(RubyString[].class, params(RubyString.class, size))
.collect(0, RubyString[].class).identity();
}

MethodHandle handle = constructRubyStringArrayHandles[size];

if (handle == null) {
try {
if (size == 0) {
handle = Binder.from(RubyString[].class).getStatic(LOOKUP, RubyString.class, "NULL_ARRAY");
} else {
handle = MethodHandles.publicLookup().findStatic(Helpers.class, "constructRubyStringArray", MethodType.methodType(RubyString[].class, CodegenUtils.params(RubyString.class, size)));
}

constructRubyStringArrayHandles[size] = handle;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

return handle;
}

public static RubyArray constructRubyArray(Ruby runtime, IRubyObject one) {
return RubyArray.newArrayLight(runtime, one);
}
Expand Down

0 comments on commit fa48c66

Please sign in to comment.