Skip to content

Commit

Permalink
Specialize dstring paths using indy
Browse files Browse the repository at this point in the history
This patch introduces a new indy call site for dynamic strings.
Instead of using frozen strings for the constant bits and doing
all construction and concatenation in the call-side bytecode, this
version embeds all constant strings into the indy call site and
passes the dynamic arguments only. This moves all concatenation
logic into the call site, where it can be specialized for small
arities or dumped into a generic large-arity string builder.
  • Loading branch information
headius committed Mar 30, 2024
1 parent 8dff34a commit 8772afa
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.jruby.compiler.impl.SkinnyMethodAdapter;
import org.jruby.ir.IRManager;
import org.jruby.ir.instructions.CallBase;
import org.jruby.ir.operands.StringLiteral;
import org.jruby.ir.operands.UndefinedValue;
import org.jruby.ir.targets.IRBytecodeAdapter;
import org.jruby.ir.targets.JVM;
Expand All @@ -23,11 +24,14 @@
import org.jruby.util.ByteList;
import org.jruby.util.CodegenUtils;

import java.lang.annotation.ElementType;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import static org.jruby.util.CodegenUtils.ci;
import static org.jruby.util.CodegenUtils.p;
import static org.jruby.util.CodegenUtils.params;
import static org.jruby.util.CodegenUtils.sig;

public class IndyValueCompiler implements ValueCompiler {
Expand Down Expand Up @@ -101,8 +105,41 @@ public void pushBufferString(Encoding encoding, int size) {
compiler.adapter.invokedynamic("bufferString", sig(RubyString.class, ThreadContext.class), StringBootstrap.BUFFER_STRING_BOOTSTRAP, encoding.toString(), size);
}

public void buildDynamicString(Encoding encoding, int size, boolean frozen, boolean debugFrozen, String file, int line, List<DStringElement> elements) {
normalValueCompiler.buildDynamicString(encoding, size, frozen, debugFrozen, file, line, elements);
public void buildDynamicString(Encoding encoding, int estimatedSize, boolean frozen, boolean debugFrozen, String file, int line, List<DStringElement> elements) {
if (elements.size() > 50) {
normalValueCompiler.buildDynamicString(encoding, estimatedSize, frozen, debugFrozen, file, line, elements);
return;
}

compiler.loadContext();

long descriptor = 0;
int bit = 0;
int otherCount = 0;
ArrayList bootstrapArgs = new ArrayList();
for (DStringElement elt: elements) {
switch (elt.type()) {
case STRING:
StringLiteral str = (StringLiteral) elt.value();
descriptor |= (1 << bit);
bootstrapArgs.add(RubyEncoding.decodeRaw(str.getByteList()));
bootstrapArgs.add(str.getByteList().getEncoding().toString());
bootstrapArgs.add(str.getCodeRange());
break;
case OTHER:
((Runnable) elt.value()).run();
otherCount++;
break;
}
bit++;
}
bootstrapArgs.add(estimatedSize * 3/2);
bootstrapArgs.add(encoding.toString());
bootstrapArgs.add(frozen);
bootstrapArgs.add(descriptor);
bootstrapArgs.add(bit);

compiler.adapter.invokedynamic("buildDynamicString", sig(RubyString.class, params(ThreadContext.class, IRubyObject.class, otherCount)), StringBootstrap.BUILD_DSTRING_BOOTSTRAP, bootstrapArgs.toArray());
}

public void pushByteList(ByteList bl) {
Expand Down
212 changes: 212 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/indy/StringBootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.jruby.RubyString;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.StringSupport;
import org.objectweb.asm.Handle;
Expand Down Expand Up @@ -64,6 +65,12 @@ public class StringBootstrap {
"fstringSimple",
sig(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, String.class, int.class),
false);
public static final Handle BUILD_DSTRING_BOOTSTRAP = new Handle(
Opcodes.H_INVOKESTATIC,
p(StringBootstrap.class),
"buildDString",
sig(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class),
false);

private static final MethodHandle STRING_HANDLE =
Binder
Expand Down Expand Up @@ -115,6 +122,211 @@ public static CallSite fstringSimple(MethodHandles.Lookup lookup, String name, M
return site;
}

public static CallSite buildDString(MethodHandles.Lookup lookup, String name, MethodType type, Object[] args) {
return new BuildDynamicStringSite(type, args);
}

public static class BuildDynamicStringSite extends MutableCallSite {
record ByteListAndCodeRange(ByteList bl, int cr) {}
final int initialSize;
final Encoding encoding;
final long descriptor;
final boolean frozen;
final int elementCount;
final ByteListAndCodeRange[] strings;

public BuildDynamicStringSite(MethodType type, Object[] stringArgs) {
super(type);

initialSize = (Integer) stringArgs[stringArgs.length - 5];
encoding = encodingFromName((String) stringArgs[stringArgs.length - 4]);
frozen = ((Integer) stringArgs[stringArgs.length - 3]) != 0;
descriptor = (Long) stringArgs[stringArgs.length - 2];
elementCount = (Integer) stringArgs[stringArgs.length - 1];

ByteListAndCodeRange[] strings = new ByteListAndCodeRange[elementCount];
int stringArgsIdx = 0;
Binder binder = Binder.from(type);

for (int i = 0; i < elementCount; i++) {
if ((descriptor & (1 << i)) != 0) {
ByteListAndCodeRange blcr = new ByteListAndCodeRange(bytelist((String) stringArgs[stringArgsIdx * 3], (String) stringArgs[stringArgsIdx * 3 + 1]), (Integer) stringArgs[stringArgsIdx * 3 + 2]);
strings[i] = blcr;
if (elementCount <= 4) {
binder = binder.insert(i + 1, blcr);
}
stringArgsIdx++;
}
}
this.strings = strings;

if (elementCount <= 4) {
// bind directly to specialized builds
binder = binder.prepend(this);
} else {
binder = binder.prepend(this).collect(2, Object[].class);
}

setTarget(binder.invokeVirtualQuiet("buildString"));
}

public RubyString buildString(ThreadContext context, IRubyObject a) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, ByteListAndCodeRange b) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.catWithCodeRange(b.bl, b.cr);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, IRubyObject b) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.appendAsDynamicString(b);

return buffer;
}

public RubyString buildString(ThreadContext context, ByteListAndCodeRange a, IRubyObject b) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.catWithCodeRange(a.bl, a.cr);
buffer.appendAsDynamicString(b);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, IRubyObject b, IRubyObject c) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.appendAsDynamicString(b);
buffer.appendAsDynamicString(c);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, IRubyObject b, ByteListAndCodeRange c) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.appendAsDynamicString(b);
buffer.catWithCodeRange(c.bl, c.cr);

return buffer;
}

public RubyString buildString(ThreadContext context, ByteListAndCodeRange a, IRubyObject b, IRubyObject c) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.catWithCodeRange(a.bl, a.cr);
buffer.appendAsDynamicString(b);
buffer.appendAsDynamicString(c);

return buffer;
}

public RubyString buildString(ThreadContext context, ByteListAndCodeRange a, IRubyObject b, ByteListAndCodeRange c) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.catWithCodeRange(a.bl, a.cr);
buffer.appendAsDynamicString(b);
buffer.catWithCodeRange(c.bl, c.cr);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, IRubyObject b, IRubyObject c, IRubyObject d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.appendAsDynamicString(b);
buffer.appendAsDynamicString(c);
buffer.appendAsDynamicString(d);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, IRubyObject b, IRubyObject c, ByteListAndCodeRange d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.appendAsDynamicString(b);
buffer.appendAsDynamicString(c);
buffer.catWithCodeRange(d.bl, d.cr);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, IRubyObject b, ByteListAndCodeRange c, IRubyObject d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.appendAsDynamicString(b);
buffer.catWithCodeRange(c.bl, c.cr);
buffer.appendAsDynamicString(d);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, ByteListAndCodeRange b, IRubyObject c, IRubyObject d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.catWithCodeRange(b.bl, b.cr);
buffer.appendAsDynamicString(c);
buffer.appendAsDynamicString(d);

return buffer;
}

public RubyString buildString(ThreadContext context, ByteListAndCodeRange a, IRubyObject b, IRubyObject c, IRubyObject d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.catWithCodeRange(a.bl, a.cr);
buffer.appendAsDynamicString(b);
buffer.appendAsDynamicString(c);
buffer.appendAsDynamicString(d);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject a, ByteListAndCodeRange b, IRubyObject c, ByteListAndCodeRange d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.appendAsDynamicString(a);
buffer.catWithCodeRange(b.bl, b.cr);
buffer.appendAsDynamicString(c);
buffer.catWithCodeRange(d.bl, d.cr);

return buffer;
}

public RubyString buildString(ThreadContext context, ByteListAndCodeRange a, IRubyObject b, ByteListAndCodeRange c, IRubyObject d) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);
buffer.catWithCodeRange(a.bl, a.cr);
buffer.appendAsDynamicString(b);
buffer.catWithCodeRange(c.bl, c.cr);
buffer.appendAsDynamicString(d);

return buffer;
}

public RubyString buildString(ThreadContext context, IRubyObject... values) {
RubyString buffer = bufferString(context, encoding, initialSize, StringSupport.CR_7BIT);

int valueIdx = 0;
for (int i = 0; i < elementCount; i++) {
if ((descriptor & (1 << i)) != 0) {
buffer.catWithCodeRange(strings[i].bl, strings[i].cr);
} else {
buffer.appendAsDynamicString(values[valueIdx++]);
}
}

if (frozen) {
buffer.freeze(context);
}

return buffer;
}
}

public static RubyString string(ThreadContext context, ByteList value, int cr) {
return RubyString.newStringShared(context.runtime, value, cr);
}
Expand Down

0 comments on commit 8772afa

Please sign in to comment.