Skip to content

Commit 93a102d

Browse files
committed
[GR-15285] Intern strings when String#-@ is called.
PullRequest: truffleruby/807
2 parents 8ffae32 + 8c5296a commit 93a102d

File tree

8 files changed

+58
-6
lines changed

8 files changed

+58
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Bug fixes:
1010
* Fixed `rb_get_kwargs` to correctly handle optional and rest arguments.
1111
* Calling `Kernel#raise` with a raised exception will no longer set the cause of the exception to itself (#1682).
1212

13+
Compatibility
14+
15+
* `String#-@` now performs string deduplication
16+
1317
# 1.0 RC 17
1418

1519
Bug fixes:
@@ -55,6 +59,7 @@ Compatibility:
5559
* `StringScanner` will now match a regexp beginning with `^` even when not scanning from the start of the string.
5660
* `Module#define_method` is now public like in MRI.
5761
* `Kernel#warn` now supports the `uplevel:` keyword argument.
62+
* `String#-@` now performs string deduplication (#1608) .
5863

5964
# 1.0 RC 15, 5 April 2019
6065

spec/ruby/core/string/uminus_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@
4242
(-dynamic).should_not equal("this string is frozen".freeze)
4343
(-dynamic).should_not equal(-"this string is frozen".freeze)
4444
end
45+
46+
it "does not deduplicate tainted strings" do
47+
dynamic = %w(this string is frozen).join(' ')
48+
dynamic.taint
49+
(-dynamic).should_not equal("this string is frozen".freeze)
50+
(-dynamic).should_not equal(-"this string is frozen".freeze)
51+
end
52+
53+
it "does not deduplicate strings with additional instance variables" do
54+
dynamic = %w(this string is frozen).join(' ')
55+
dynamic.instance_variable_set(:@foo, :bar)
56+
(-dynamic).should_not equal("this string is frozen".freeze)
57+
(-dynamic).should_not equal(-"this string is frozen".freeze)
58+
end
4559
end
4660

4761
ruby_version_is "2.6" do

spec/tags/core/string/uminus_tags.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.truffleruby.core.rope.PathToRopeCache;
4747
import org.truffleruby.core.rope.Rope;
4848
import org.truffleruby.core.rope.RopeCache;
49+
import org.truffleruby.core.rope.RopeKey;
4950
import org.truffleruby.core.string.CoreStrings;
5051
import org.truffleruby.core.string.FrozenStringLiterals;
5152
import org.truffleruby.core.symbol.SymbolTable;
@@ -637,6 +638,10 @@ public DynamicObject getFrozenStringLiteral(Rope rope) {
637638
return frozenStringLiterals.getFrozenStringLiteral(rope);
638639
}
639640

641+
public DynamicObject getInternedString(DynamicObject string) {
642+
return frozenStringLiterals.getFrozenStringLiteral(string);
643+
}
644+
640645
public Object getClassVariableDefinitionLock() {
641646
return classVariableDefinitionLock;
642647
}

src/main/java/org/truffleruby/collections/WeakValueCache.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import java.util.Map.Entry;
3939
import java.util.concurrent.ConcurrentHashMap;
4040

41+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
42+
4143
import org.truffleruby.core.hash.ReHashable;
4244

4345
/**
@@ -65,6 +67,7 @@ public Value get(Key key) {
6567
* Returns the value in the cache (existing or added).
6668
* Similar to a putIfAbsent() but always return the value in the cache.
6769
*/
70+
@TruffleBoundary
6871
public Value addInCacheIfAbsent(Key key, Value newValue) {
6972
removeStaleEntries();
7073

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,36 @@
1212
import java.util.Map;
1313
import java.util.concurrent.ConcurrentHashMap;
1414

15+
import org.truffleruby.Layouts;
1516
import org.truffleruby.RubyContext;
1617
import org.truffleruby.collections.ConcurrentOperations;
18+
import org.truffleruby.collections.WeakValueCache;
1719
import org.truffleruby.core.rope.Rope;
20+
import org.truffleruby.core.rope.RopeKey;
1821

1922
import com.oracle.truffle.api.object.DynamicObject;
2023

2124
public class FrozenStringLiterals {
2225

2326
private final RubyContext context;
24-
private final Map<Rope, DynamicObject> strings = new ConcurrentHashMap<>();
27+
private final WeakValueCache<RopeKey, DynamicObject> values = new WeakValueCache<>();
2528

2629
public FrozenStringLiterals(RubyContext context) {
2730
this.context = context;
2831
}
2932

3033
public DynamicObject getFrozenStringLiteral(Rope rope) {
31-
return ConcurrentOperations.getOrCompute(strings, rope, r -> StringOperations.createFrozenString(context, rope));
34+
final RopeKey key = new RopeKey(rope, context.getHashing(values));
35+
return values.addInCacheIfAbsent(key, StringOperations.createFrozenString(context, rope));
36+
}
37+
38+
public DynamicObject getFrozenStringLiteral(DynamicObject string) {
39+
assert Layouts.STRING.getFrozen(string) == true;
40+
41+
final Rope rope = Layouts.STRING.getRope(string);
42+
final RopeKey key = new RopeKey(rope, context.getHashing(values));
43+
44+
return values.addInCacheIfAbsent(key, string);
3245
}
3346

3447
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,10 @@
125125
import org.truffleruby.core.rope.RopeBuilder;
126126
import org.truffleruby.core.rope.RopeConstants;
127127
import org.truffleruby.core.rope.RopeGuards;
128+
import org.truffleruby.core.rope.RopeKey;
128129
import org.truffleruby.core.rope.RopeNodes;
129130
import org.truffleruby.core.rope.RopeNodes.RepeatNode;
131+
import org.truffleruby.core.rope.TruffleRopesNodes.FlattenRopeNode;
130132
import org.truffleruby.core.rope.RopeOperations;
131133
import org.truffleruby.core.rope.SubstringRope;
132134
import org.truffleruby.core.string.StringNodesFactory.ByteIndexFromCharIndexNodeGen;
@@ -4712,4 +4714,13 @@ protected Object emptyString(DynamicObject string) {
47124714

47134715
}
47144716

4717+
@Primitive(name = "string_intern", needsSelf = false)
4718+
public abstract static class InternNode extends PrimitiveArrayArgumentsNode {
4719+
4720+
@Specialization
4721+
public DynamicObject internString(DynamicObject string) {
4722+
return getContext().getInternedString(string);
4723+
}
4724+
}
4725+
47154726
}

src/main/ruby/core/string.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,11 @@ def +@
15901590
end
15911591

15921592
def -@
1593-
frozen? ? self : dup.freeze
1593+
str = frozen? ? self : dup.freeze
1594+
unless str.tainted? || !(str.instance_variables).empty?
1595+
Truffle::Ropes.flatten_rope(str)
1596+
Truffle.invoke_primitive(:string_intern, str)
1597+
end
15941598
end
15951599

15961600
def encoding

0 commit comments

Comments
 (0)