Skip to content

Commit a719174

Browse files
committed
Do not make the RubyString native when passed to a :string argument of a FFI call
* See #3293 (comment) and ffi/ffi#1080 * As that could cause extra conversions to managed later on.
1 parent b7cb26e commit a719174

File tree

6 files changed

+53
-27
lines changed

6 files changed

+53
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Performance:
6464
* Change the `Hash` representation from traditional buckets to a "compact hash table" for improved locality, performance and memory footprint (#3172, @moste00).
6565
* Optimize calls with `ruby2_keywords` forwarding by deciding it per call site instead of per callee thanks to [my fix in CRuby 3.2](https://bugs.ruby-lang.org/issues/18625) (@eregon).
6666
* Optimize feature loading when require is called with an absolute path to a .rb file (@rwstauner).
67+
* Avoid extra copies for Strings passed as `:string` arguments to a FFI call and used later for Regexp matching (#3293, @eregon).
6768

6869
Changes:
6970

lib/truffle/truffle/ffi_backend/function.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def free
155155
get_pointer_value(value)
156156
elsif FFI::Type::STRING == type
157157
Truffle::Type.check_null_safe(value) unless Primitive.nil?(value)
158-
get_pointer_value(value)
158+
Truffle::CExt.string_to_ffi_pointer_copy(value)
159159
elsif Primitive.is_a?(type, FFI::FunctionType) and Primitive.is_a?(value, Proc)
160160
callback(value, type)
161161
else
@@ -198,7 +198,7 @@ def free
198198
elsif Primitive.nil?(value)
199199
Truffle::FFI::Pointer::NULL
200200
elsif Primitive.is_a?(value, String)
201-
Truffle::CExt.string_to_ffi_pointer(value)
201+
Truffle::CExt.string_to_ffi_pointer_inplace(value)
202202
elsif value.respond_to?(:to_ptr)
203203
Truffle::Type.coerce_to value, Truffle::FFI::Pointer, :to_ptr
204204
else

lib/truffle/truffle/fiddle_backend.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def self.convert_ruby_to_native(type, val)
9393

9494
def self.get_pointer_value(val)
9595
if Primitive.is_a?(val, String)
96-
Truffle::CExt.string_to_ffi_pointer(val)
96+
# NOTE: Fiddle::TYPE_CONST_STRING wouldn't need inplace, but not defined yet by this file
97+
Truffle::CExt.string_to_ffi_pointer_inplace(val)
9798
elsif Primitive.is_a?(val, Fiddle::Pointer)
9899
val.to_i
99100
elsif val.respond_to?(:to_ptr)

src/main/java/org/truffleruby/cext/CExtNodes.java

+42-17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Arrays;
1414
import java.util.concurrent.locks.ReentrantLock;
1515

16+
import com.oracle.truffle.api.CompilerAsserts;
1617
import com.oracle.truffle.api.TruffleSafepoint;
1718
import com.oracle.truffle.api.dsl.Bind;
1819
import com.oracle.truffle.api.dsl.GenerateCached;
@@ -714,7 +715,7 @@ public abstract static class RbEncCodeRangeClear extends CoreMethodArrayArgument
714715
@Specialization
715716
RubyString clearCodeRange(RubyString string,
716717
@Cached StringToNativeNode stringToNativeNode) {
717-
stringToNativeNode.executeToNative(this, string);
718+
stringToNativeNode.executeToNative(this, string, true);
718719
string.clearCodeRange();
719720

720721
return string;
@@ -817,7 +818,7 @@ public abstract static class RbStrCapacityNode extends CoreMethodArrayArgumentsN
817818
@Specialization
818819
long capacity(Object string,
819820
@Cached StringToNativeNode stringToNativeNode) {
820-
return getNativeStringCapacity(stringToNativeNode.executeToNative(this, string));
821+
return getNativeStringCapacity(stringToNativeNode.executeToNative(this, string, true));
821822
}
822823
}
823824

@@ -830,7 +831,7 @@ RubyString strSetLen(RubyString string, int newByteLength,
830831
@Cached StringToNativeNode stringToNativeNode,
831832
@Cached MutableTruffleString.FromNativePointerNode fromNativePointerNode,
832833
@Cached InlinedConditionProfile minLengthOneProfile) {
833-
var pointer = stringToNativeNode.executeToNative(this, string);
834+
var pointer = stringToNativeNode.executeToNative(this, string, true);
834835

835836
var encoding = libString.getEncoding(string);
836837
int minLength = encoding.jcoding.minLength();
@@ -860,7 +861,7 @@ RubyString rbStrResize(RubyString string, int newByteLength,
860861
@Cached RubyStringLibrary libString,
861862
@Cached StringToNativeNode stringToNativeNode,
862863
@Cached MutableTruffleString.FromNativePointerNode fromNativePointerNode) {
863-
var pointer = stringToNativeNode.executeToNative(this, string);
864+
var pointer = stringToNativeNode.executeToNative(this, string, true);
864865
var tencoding = libString.getTEncoding(string);
865866
int byteLength = string.tstring.byteLength(tencoding);
866867

@@ -889,7 +890,7 @@ RubyString trStrCapaResize(RubyString string, int newCapacity,
889890
@Cached RubyStringLibrary libString,
890891
@Cached StringToNativeNode stringToNativeNode,
891892
@Cached MutableTruffleString.FromNativePointerNode fromNativePointerNode) {
892-
var pointer = stringToNativeNode.executeToNative(this, string);
893+
var pointer = stringToNativeNode.executeToNative(this, string, true);
893894
var tencoding = libString.getTEncoding(string);
894895

895896
if (getNativeStringCapacity(pointer) == newCapacity) {
@@ -1327,24 +1328,29 @@ int rbHash(Object object,
13271328
}
13281329
}
13291330

1331+
/** If inplace is true, this node mutates the RubyString to use native memory. It should be avoided unless there is
1332+
* no other way because e.g. Regexp matching later on that String would then copy to managed byte[] back, and
1333+
* copying back-and-forth is quite expensive. OTOH if the String will need to be used as native memory again soon
1334+
* after and without needing to go to managed in between then it is valuable to avoid extra copies. */
13301335
@GenerateInline
13311336
@GenerateCached(false)
13321337
public abstract static class StringToNativeNode extends RubyBaseNode {
13331338

1334-
public abstract Pointer executeToNative(Node node, Object string);
1339+
public abstract Pointer executeToNative(Node node, Object string, boolean inplace);
13351340

13361341
@Specialization
1337-
static Pointer toNative(Node node, RubyString string,
1342+
static Pointer toNative(Node node, RubyString string, boolean inplace,
13381343
@Cached RubyStringLibrary libString,
13391344
@Cached InlinedConditionProfile convertProfile,
13401345
@Cached(inline = false) TruffleString.CopyToNativeMemoryNode copyToNativeMemoryNode,
13411346
@Cached(inline = false) MutableTruffleString.FromNativePointerNode fromNativePointerNode,
13421347
@Cached(inline = false) TruffleString.GetInternalNativePointerNode getInternalNativePointerNode) {
1348+
CompilerAsserts.partialEvaluationConstant(inplace);
1349+
13431350
var tstring = string.tstring;
13441351
var tencoding = libString.getTEncoding(string);
13451352

13461353
final Pointer pointer;
1347-
13481354
if (convertProfile.profile(node, tstring.isNative())) {
13491355
assert tstring.isMutable();
13501356
pointer = (Pointer) getInternalNativePointerNode.execute(tstring, tencoding);
@@ -1353,16 +1359,18 @@ static Pointer toNative(Node node, RubyString string,
13531359
pointer = allocateAndCopyToNative(getLanguage(node), getContext(node), tstring, tencoding, byteLength,
13541360
copyToNativeMemoryNode);
13551361

1356-
var nativeTString = fromNativePointerNode.execute(pointer, 0, byteLength, tencoding, false);
1357-
string.setTString(nativeTString);
1362+
if (inplace) {
1363+
var nativeTString = fromNativePointerNode.execute(pointer, 0, byteLength, tencoding, false);
1364+
string.setTString(nativeTString);
1365+
}
13581366
}
13591367

13601368
return pointer;
13611369
}
13621370

13631371
@Specialization
1364-
static Pointer toNativeImmutable(Node node, ImmutableRubyString string) {
1365-
return string.getNativeString(getLanguage(node));
1372+
static Pointer toNativeImmutable(Node node, ImmutableRubyString string, boolean inplace) {
1373+
return string.getNativeString(getLanguage(node), getContext(node));
13661374
}
13671375

13681376
public static Pointer allocateAndCopyToNative(RubyLanguage language, RubyContext context,
@@ -1381,17 +1389,34 @@ public abstract static class StringPointerToNativeNode extends PrimitiveArrayArg
13811389
@Specialization
13821390
long toNative(Object string,
13831391
@Cached StringToNativeNode stringToNativeNode) {
1384-
return stringToNativeNode.executeToNative(this, string).getAddress();
1392+
return stringToNativeNode.executeToNative(this, string, true).getAddress();
1393+
}
1394+
}
1395+
1396+
@CoreMethod(names = "string_to_ffi_pointer_inplace", onSingleton = true, required = 1)
1397+
public abstract static class StringToFFIPointerInplaceNode extends CoreMethodArrayArgumentsNode {
1398+
1399+
@Specialization
1400+
RubyPointer toFFIPointerInplace(Object string,
1401+
@Cached StringToNativeNode stringToNativeNode) {
1402+
var pointer = stringToNativeNode.executeToNative(this, string, true);
1403+
1404+
final RubyPointer instance = new RubyPointer(
1405+
coreLibrary().truffleFFIPointerClass,
1406+
getLanguage().truffleFFIPointerShape,
1407+
pointer);
1408+
AllocationTracing.trace(instance, this);
1409+
return instance;
13851410
}
13861411
}
13871412

1388-
@CoreMethod(names = "string_to_ffi_pointer", onSingleton = true, required = 1)
1389-
public abstract static class StringToFFIPointerNode extends CoreMethodArrayArgumentsNode {
1413+
@CoreMethod(names = "string_to_ffi_pointer_copy", onSingleton = true, required = 1)
1414+
public abstract static class StringToFFIPointerCopyNode extends CoreMethodArrayArgumentsNode {
13901415

13911416
@Specialization
1392-
RubyPointer toNative(Object string,
1417+
RubyPointer toFFIPointerCopy(Object string,
13931418
@Cached StringToNativeNode stringToNativeNode) {
1394-
var pointer = stringToNativeNode.executeToNative(this, string);
1419+
var pointer = stringToNativeNode.executeToNative(this, string, false);
13951420

13961421
final RubyPointer instance = new RubyPointer(
13971422
coreLibrary().truffleFFIPointerClass,

src/main/java/org/truffleruby/core/format/convert/StringToPointerNode.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ static long toPointer(VirtualFrame frame, Object string,
4242
@Cached RubyStringLibrary strings,
4343
@Bind("this") Node node) {
4444

45-
final Pointer pointer = stringToNativeNode.executeToNative(node, string);
45+
final Pointer pointer = stringToNativeNode.executeToNative(node, string, true);
4646

4747
List<Pointer> associated = (List<Pointer>) frame.getObject(FormatFrameDescriptor.ASSOCIATED_SLOT);
4848

src/main/java/org/truffleruby/core/string/ImmutableRubyString.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,20 @@ public boolean isNative() {
8080
}
8181

8282
@SuppressFBWarnings("IS2_INCONSISTENT_SYNC")
83-
public Pointer getNativeString(RubyLanguage language) {
83+
public Pointer getNativeString(RubyLanguage language, RubyContext context) {
8484
if (nativeString == null) {
85-
return createNativeString(language);
85+
return createNativeString(language, context);
8686
}
8787
return nativeString;
8888
}
8989

9090
@TruffleBoundary
91-
private synchronized Pointer createNativeString(RubyLanguage language) {
91+
private synchronized Pointer createNativeString(RubyLanguage language, RubyContext context) {
9292
if (nativeString == null) {
9393
var tencoding = getEncodingUncached().tencoding;
9494
int byteLength = tstring.byteLength(tencoding);
95-
nativeString = CExtNodes.StringToNativeNode.allocateAndCopyToNative(language,
96-
RubyLanguage.getCurrentContext(), tstring, tencoding, byteLength,
97-
TruffleString.CopyToNativeMemoryNode.getUncached());
95+
nativeString = CExtNodes.StringToNativeNode.allocateAndCopyToNative(language, context, tstring, tencoding,
96+
byteLength, TruffleString.CopyToNativeMemoryNode.getUncached());
9897
}
9998
return nativeString;
10099
}

0 commit comments

Comments
 (0)