Skip to content

Commit 2742327

Browse files
committed
[GR-35145] Shared array strategy.
PullRequest: truffleruby/3005
2 parents 0e42c2a + 3e6e8eb commit 2742327

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+941
-399
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Changes:
2929
* Removed `Truffle::Interop.{import_without_conversion,export_without_conversion}` (use `Polyglot.{import,export}` instead).
3030
* Removed `Truffle::Interop.members_without_conversion` (use `Truffle::Interop.members` instead).
3131
* Refactored internals of `rb_sprintf` to simplify handling of `VALUE`s in common cases (@aardvark179).
32+
* Refactored sharing of array objects between threads using new `SharedArrayStorage` (@aardvark179).
3233

3334
# 22.2.0
3435

spec/truffle/thread_safe_objects_spec.rb

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,11 @@ def wb; @wb; end
162162
@share = ary
163163
shared?(ary).should == true
164164

165-
new_ary = []
165+
new_ary = [Object.new]
166166
ary.replace(new_ary)
167-
shared?(new_ary).should == true
167+
# new_ary is not shared, but any object it contains should be.
168+
shared?(new_ary).should == false
169+
new_ary.each { |e| shared?(e).should == true }
168170
end
169171

170172
it "Array :steal_array_storage" do
@@ -189,6 +191,40 @@ def wb; @wb; end
189191
ary.each { |e| shared?(e).should == true }
190192
end
191193

194+
it "Array#shift(n) with copy-on-write DelegatedArrayStorage" do
195+
ary = [Object.new, Object.new, Object.new, Object.new, Object.new]
196+
@share = ary
197+
new_ary = ary.shift(3)
198+
shared?(ary).should == true
199+
ary.each { |e| shared?(e).should == true }
200+
201+
shared?(new_ary).should == false
202+
new_ary.each { |e| shared?(e).should == true }
203+
end
204+
205+
it "Array#pop with copy-on-write DelegatedArrayStorage" do
206+
ary = [Object.new, Object.new, Object.new]
207+
ary.pop
208+
@share = ary
209+
shared?(ary).should == true
210+
ary.each { |e| shared?(e).should == true }
211+
212+
wb = Object.new
213+
ary[0] = wb
214+
ary.each { |e| shared?(e).should == true }
215+
end
216+
217+
it "Array#pop(n) with copy-on-write DelegatedArrayStorage" do
218+
ary = [Object.new, Object.new, Object.new, Object.new, Object.new]
219+
@share = ary
220+
new_ary = ary.pop(3)
221+
shared?(ary).should == true
222+
ary.each { |e| shared?(e).should == true }
223+
224+
shared?(new_ary).should == false
225+
new_ary.each { |e| shared?(e).should == true }
226+
end
227+
192228
it "Hash" do
193229
initial = Object.new
194230
hsh = { initial => Object.new }

src/main/java/org/truffleruby/core/CoreLibrary.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ public CoreLibrary(RubyContext context, RubyLanguage language) {
544544
// Create some key objects
545545

546546
mainObject = new RubyBasicObject(objectClass, language.basicObjectShape);
547-
argv = new RubyArray(arrayClass, language.arrayShape, ArrayStoreLibrary.INITIAL_STORE, 0);
547+
argv = new RubyArray(arrayClass, language.arrayShape, ArrayStoreLibrary.initialStorage(false), 0);
548548

549549
globalVariables = new GlobalVariables(context);
550550
interactiveBindingLocalVariablesObject = new BindingLocalVariablesObject();

src/main/java/org/truffleruby/core/array/ArrayAppendManyNode.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import org.truffleruby.core.array.library.ArrayStoreLibrary;
1616
import org.truffleruby.language.RubyBaseNode;
17-
import org.truffleruby.language.objects.shared.PropagateSharingNode;
1817

1918
import com.oracle.truffle.api.dsl.Bind;
2019
import com.oracle.truffle.api.dsl.Cached;
@@ -26,8 +25,6 @@
2625
@ImportStatic(ArrayGuards.class)
2726
public abstract class ArrayAppendManyNode extends RubyBaseNode {
2827

29-
@Child private PropagateSharingNode propagateSharingNode = PropagateSharingNode.create();
30-
3128
public abstract RubyArray executeAppendMany(RubyArray array, RubyArray other);
3229

3330
// Append of a compatible type
@@ -40,11 +37,11 @@ protected RubyArray appendZero(RubyArray array, RubyArray other) {
4037
}
4138

4239
@Specialization(
43-
guards = { "!isEmptyArray(other)", "stores.acceptsAllValues(array.store, other.store)" },
40+
guards = { "!isEmptyArray(other)", "stores.acceptsAllValues(array.getStore(), other.getStore())" },
4441
limit = "storageStrategyLimit()")
4542
protected RubyArray appendManySameType(RubyArray array, RubyArray other,
46-
@Bind("array.store") Object store,
47-
@Bind("other.store") Object otherStore,
43+
@Bind("array.getStore()") Object store,
44+
@Bind("other.getStore()") Object otherStore,
4845
@CachedLibrary("store") ArrayStoreLibrary stores,
4946
@CachedLibrary("otherStore") ArrayStoreLibrary otherStores,
5047
@Cached ConditionProfile extendProfile) {
@@ -53,7 +50,6 @@ protected RubyArray appendManySameType(RubyArray array, RubyArray other,
5350
final int newSize = oldSize + otherSize;
5451
final int length = stores.capacity(store);
5552

56-
propagateSharingNode.executePropagate(array, other);
5753
if (extendProfile.profile(newSize > length)) {
5854
final int capacity = ArrayUtils.capacity(getLanguage(), length, newSize);
5955
Object newStore = stores.expand(store, capacity);
@@ -69,19 +65,18 @@ protected RubyArray appendManySameType(RubyArray array, RubyArray other,
6965
// Generalizations
7066

7167
@Specialization(
72-
guards = { "!isEmptyArray(other)", "!stores.acceptsAllValues(array.store, other.store)" },
68+
guards = { "!isEmptyArray(other)", "!stores.acceptsAllValues(array.getStore(), other.getStore())" },
7369
limit = "storageStrategyLimit()")
7470
protected RubyArray appendManyGeneralize(RubyArray array, RubyArray other,
75-
@Bind("array.store") Object store,
76-
@Bind("other.store") Object otherStore,
71+
@Bind("array.getStore()") Object store,
72+
@Bind("other.getStore()") Object otherStore,
7773
@CachedLibrary("store") ArrayStoreLibrary stores,
7874
@CachedLibrary("otherStore") ArrayStoreLibrary otherStores) {
7975
final int oldSize = array.size;
8076
final int otherSize = other.size;
8177
final int newSize = oldSize + otherSize;
8278
final Object newStore = stores.allocateForNewStore(store, otherStore, newSize);
8379

84-
propagateSharingNode.executePropagate(array, other);
8580
stores.copyContents(store, 0, newStore, 0, oldSize);
8681
otherStores.copyContents(otherStore, 0, newStore, oldSize, otherSize);
8782
setStoreAndSize(array, newStore, newSize);

src/main/java/org/truffleruby/core/array/ArrayAppendOneNode.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import org.truffleruby.core.array.library.ArrayStoreLibrary;
1616
import org.truffleruby.language.RubyContextSourceNode;
1717
import org.truffleruby.language.RubyNode;
18-
import org.truffleruby.language.objects.shared.PropagateSharingNode;
1918

2019
import com.oracle.truffle.api.dsl.Bind;
2120
import com.oracle.truffle.api.dsl.Cached;
@@ -30,8 +29,6 @@
3029
@ImportStatic(ArrayGuards.class)
3130
public abstract class ArrayAppendOneNode extends RubyContextSourceNode {
3231

33-
@Child private PropagateSharingNode propagateSharingNode = PropagateSharingNode.create();
34-
3532
public static ArrayAppendOneNode create() {
3633
return ArrayAppendOneNodeGen.create(null, null);
3734
}
@@ -46,15 +43,13 @@ public static ArrayAppendOneNode create() {
4643
guards = { "stores.acceptsValue(store, value)" },
4744
limit = "storageStrategyLimit()")
4845
protected RubyArray appendOneSameType(RubyArray array, Object value,
49-
@Bind("array.store") Object store,
46+
@Bind("array.getStore()") Object store,
5047
@CachedLibrary("store") ArrayStoreLibrary stores,
5148
@Cached("createCountingProfile()") ConditionProfile extendProfile) {
5249
final int oldSize = array.size;
5350
final int newSize = oldSize + 1;
5451
final int length = stores.capacity(store);
5552

56-
propagateSharingNode.executePropagate(array, value);
57-
5853
if (extendProfile.profile(newSize > length)) {
5954
final int capacity = ArrayUtils.capacityForOneMore(getLanguage(), length);
6055
final Object newStore = stores.expand(store, capacity);
@@ -70,10 +65,10 @@ protected RubyArray appendOneSameType(RubyArray array, Object value,
7065
// Append forcing a generalization
7166

7267
@Specialization(
73-
guards = "!currentStores.acceptsValue(array.store, value)",
68+
guards = "!currentStores.acceptsValue(array.getStore(), value)",
7469
limit = "storageStrategyLimit()")
7570
protected RubyArray appendOneGeneralizeNonMutable(RubyArray array, Object value,
76-
@Bind("array.store") Object currentStore,
71+
@Bind("array.getStore()") Object currentStore,
7772
@CachedLibrary("currentStore") ArrayStoreLibrary currentStores,
7873
@CachedLibrary(limit = "storageStrategyLimit()") ArrayStoreLibrary newStores) {
7974
final int oldSize = array.size;
@@ -84,7 +79,6 @@ protected RubyArray appendOneGeneralizeNonMutable(RubyArray array, Object value,
8479
: oldCapacity;
8580
final Object newStore = currentStores.allocateForNewValue(currentStore, value, newCapacity);
8681
currentStores.copyContents(currentStore, 0, newStore, 0, oldSize);
87-
propagateSharingNode.executePropagate(array, value);
8882
newStores.write(newStore, oldSize, value);
8983
setStoreAndSize(array, newStore, newSize);
9084
return array;

src/main/java/org/truffleruby/core/array/ArrayBuilderNode.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static ArrayBuilderNode create() {
5858

5959
private static class ArrayBuilderProxyNode extends ArrayBuilderNode {
6060

61-
@Child StartNode startNode = new StartNode(ArrayStoreLibrary.INITIAL_ALLOCATOR, 0);
61+
@Child StartNode startNode = new StartNode(ArrayStoreLibrary.initialAllocator(false), 0);
6262
@Child AppendArrayNode appendArrayNode;
6363
@Child AppendOneNode appendOneNode;
6464

@@ -160,7 +160,7 @@ public StartNode(ArrayStoreLibrary.ArrayAllocator allocator, int expectedLength)
160160
}
161161

162162
public BuilderState start() {
163-
if (allocator == ArrayStoreLibrary.INITIAL_ALLOCATOR) {
163+
if (allocator == ArrayStoreLibrary.initialAllocator(false)) {
164164
return new BuilderState(allocator.allocate(0), expectedLength);
165165
} else {
166166
return new BuilderState(allocator.allocate(expectedLength), expectedLength);
@@ -172,13 +172,12 @@ public BuilderState start(int length) {
172172
CompilerDirectives.transferToInterpreterAndInvalidate();
173173
replaceNodes(allocator, length);
174174
}
175-
if (allocator == ArrayStoreLibrary.INITIAL_ALLOCATOR) {
175+
if (allocator == ArrayStoreLibrary.initialAllocator(false)) {
176176
return new BuilderState(allocator.allocate(0), length);
177177
} else {
178178
return new BuilderState(allocator.allocate(length), length);
179179
}
180180
}
181-
182181
}
183182

184183
@ImportStatic(ArrayGuards.class)
@@ -251,11 +250,11 @@ public static AppendArrayNode create() {
251250
public abstract void executeAppend(BuilderState state, int index, RubyArray value);
252251

253252
@Specialization(
254-
guards = { "arrays.acceptsAllValues(state.store, other.store)" },
253+
guards = { "arrays.acceptsAllValues(state.store, other.getStore())" },
255254
limit = "storageStrategyLimit()")
256255
protected void appendCompatibleStrategy(BuilderState state, int index, RubyArray other,
257256
@Bind("state.store") Object store,
258-
@Bind("other.store") Object otherStore,
257+
@Bind("other.getStore()") Object otherStore,
259258
@CachedLibrary("store") ArrayStoreLibrary arrays,
260259
@CachedLibrary("otherStore") ArrayStoreLibrary others) {
261260
assert state.nextIndex == index;
@@ -276,7 +275,7 @@ protected void appendCompatibleStrategy(BuilderState state, int index, RubyArray
276275
}
277276

278277
@Specialization(
279-
guards = { "!arrayLibrary.acceptsAllValues(state.store, other.store)" },
278+
guards = { "!arrayLibrary.acceptsAllValues(state.store, other.getStore())" },
280279
limit = "1")
281280
protected void appendNewStrategy(BuilderState state, int index, RubyArray other,
282281
@Bind("state.store") Object store,
@@ -299,13 +298,13 @@ protected void appendNewStrategy(BuilderState state, int index, RubyArray other,
299298
}
300299

301300
ArrayAllocator allocator = replaceNodes(
302-
newArrayLibrary.generalizeForStore(state.store, other.store),
301+
newArrayLibrary.generalizeForStore(state.store, other.getStore()),
303302
neededCapacity);
304303
newStore = allocator.allocate(neededCapacity);
305304

306305
newArrayLibrary.copyContents(state.store, 0, newStore, 0, index);
307306

308-
final Object otherStore = other.store;
307+
final Object otherStore = other.getStore();
309308
newArrayLibrary.copyContents(otherStore, 0, newStore, index, otherSize);
310309

311310
state.store = newStore;

src/main/java/org/truffleruby/core/array/ArrayCopyCompatibleRangeNode.java

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,13 @@
99
*/
1010
package org.truffleruby.core.array;
1111

12-
import com.oracle.truffle.api.TruffleSafepoint;
13-
import com.oracle.truffle.api.profiles.LoopConditionProfile;
1412
import org.truffleruby.core.array.library.ArrayStoreLibrary;
1513
import org.truffleruby.language.RubyBaseNode;
16-
import org.truffleruby.language.objects.shared.WriteBarrierNode;
1714

18-
import com.oracle.truffle.api.dsl.Cached;
1915
import com.oracle.truffle.api.dsl.ImportStatic;
2016
import com.oracle.truffle.api.dsl.ReportPolymorphism;
2117
import com.oracle.truffle.api.dsl.Specialization;
2218
import com.oracle.truffle.api.library.CachedLibrary;
23-
import com.oracle.truffle.api.profiles.ConditionProfile;
2419

2520

2621
/** Copies a portion of an array to another array, whose store is known to have sufficient capacity, and to be
@@ -39,54 +34,22 @@ public static ArrayCopyCompatibleRangeNode create() {
3934
return ArrayCopyCompatibleRangeNodeGen.create();
4035
}
4136

42-
public abstract void execute(Object dstStore, Object srcStore, int dstStart, int srcStart, int length,
43-
boolean dstShared, boolean srcShared);
37+
public abstract void execute(Object dstStore, Object srcStore, int dstStart, int srcStart, int length);
4438

4539
protected boolean noopGuard(Object dstStore, Object srcStore, int dstStart, int srcStart, int length) {
4640
return length == 0 || dstStore == srcStore && dstStart == srcStart;
4741
}
4842

4943
@Specialization(guards = "noopGuard(dstStore, srcStore, dstStart, srcStart, length)")
50-
protected void noop(
51-
Object dstStore,
52-
Object srcStore,
53-
int dstStart,
54-
int srcStart,
55-
int length,
56-
boolean dstShared,
57-
boolean srcShared) {
44+
protected void noop(Object dstStore, Object srcStore, int dstStart, int srcStart, int length) {
5845
}
5946

6047
@Specialization(
6148
guards = "!noopGuard(dstStore, srcStore, dstStart, srcStart, length)",
6249
limit = "storageStrategyLimit()")
63-
protected void copy(
64-
Object dstStore,
65-
Object srcStore,
66-
int dstStart,
67-
int srcStart,
68-
int length,
69-
boolean dstShared,
70-
boolean srcShared,
71-
@CachedLibrary("srcStore") ArrayStoreLibrary stores,
72-
@Cached WriteBarrierNode writeBarrierNode,
73-
@Cached ConditionProfile share,
74-
@Cached LoopConditionProfile loopProfile) {
50+
protected void copy(Object dstStore, Object srcStore, int dstStart, int srcStart, int length,
51+
@CachedLibrary("srcStore") ArrayStoreLibrary stores) {
7552

7653
stores.copyContents(srcStore, srcStart, dstStore, dstStart, length);
77-
78-
if (share.profile(!stores.isPrimitive(srcStore) &&
79-
dstShared &&
80-
!srcShared)) {
81-
int i = 0;
82-
try {
83-
for (; loopProfile.inject(i < length); ++i) {
84-
writeBarrierNode.executeWriteBarrier(stores.read(srcStore, srcStart + i));
85-
}
86-
TruffleSafepoint.poll(this);
87-
} finally {
88-
profileAndReportLoopCount(loopProfile, i);
89-
}
90-
}
9154
}
9255
}

src/main/java/org/truffleruby/core/array/ArrayCopyOnWriteNode.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,21 @@ public static ArrayCopyOnWriteNode create() {
3131

3232
@Specialization(guards = "stores.isMutable(store)", limit = "storageStrategyLimit()")
3333
protected Object extractFromMutableArray(RubyArray array, int start, int length,
34-
@Bind("array.store") Object store,
34+
@Bind("array.getStore()") Object store,
3535
@CachedLibrary("store") ArrayStoreLibrary stores) {
3636
int size = array.size;
3737
Object cowStore = stores.extractRange(store, 0, size);
38-
Object range = stores.extractRange(store, start, start + length);
39-
array.store = cowStore;
38+
/* This new store should not be shared because the RubyArray object which will own it is not yet shared. */
39+
Object range = stores.extractRangeAndUnshare(store, start, start + length);
40+
array.setStore(cowStore);
4041
return range;
4142
}
4243

4344
@Specialization(guards = "!stores.isMutable(store)", limit = "storageStrategyLimit()")
4445
protected Object extractFromNonMutableArray(RubyArray array, int start, int length,
45-
@Bind("array.store") Object store,
46+
@Bind("array.getStore()") Object store,
4647
@CachedLibrary("store") ArrayStoreLibrary stores) {
47-
Object range = stores.extractRange(store, start, start + length);
48+
Object range = stores.extractRangeAndUnshare(store, start, start + length);
4849
return range;
4950
}
5051
}

src/main/java/org/truffleruby/core/array/ArrayDupNode.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public abstract class ArrayDupNode extends RubyBaseNode {
3434
"cachedSize <= MAX_EXPLODE_SIZE" },
3535
limit = "getCacheLimit()")
3636
protected RubyArray dupProfiledSize(RubyArray from,
37-
@Bind("from.store") Object fromStore,
37+
@Bind("from.getStore()") Object fromStore,
3838
@CachedLibrary("fromStore") ArrayStoreLibrary fromStores,
3939
@CachedLibrary(limit = "1") ArrayStoreLibrary toStores,
4040
@Cached("from.size") int cachedSize) {
@@ -47,7 +47,7 @@ private RubyArray copyArraySmall(RubyLanguage language,
4747
ArrayStoreLibrary toStores,
4848
Object fromStore,
4949
int cachedSize) {
50-
final Object copy = fromStores.allocator(fromStore).allocate(cachedSize);
50+
final Object copy = fromStores.unsharedAllocator(fromStore).allocate(cachedSize);
5151
for (int i = 0; i < cachedSize; i++) {
5252
toStores.write(copy, i, fromStores.read(fromStore, i));
5353
}
@@ -56,7 +56,7 @@ private RubyArray copyArraySmall(RubyLanguage language,
5656

5757
@Specialization(replaces = "dupProfiledSize")
5858
protected RubyArray dup(RubyArray from,
59-
@Bind("from.store") Object fromStore,
59+
@Bind("from.getStore()") Object fromStore,
6060
@Cached ArrayCopyOnWriteNode cowNode) {
6161
final int size = from.size;
6262
final Object copy = cowNode.execute(from, 0, from.size);

0 commit comments

Comments
 (0)