Skip to content

Commit c622cf9

Browse files
committed
[GR-25848] Reduce splitting required for special variable access.
PullRequest: truffleruby/1990
2 parents 22858e5 + f977218 commit c622cf9

Some content is hidden

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

54 files changed

+752
-337
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Performance:
5555
* Calls with a literal block are no longer always split but instead the decision is made by the Truffle splitting heuristic.
5656
* `Symbol#to_proc` is now AST-inlined in order to not rely on splitting and to avoid needing the caller frame to find refinements which apply.
5757
* `Symbol#to_proc` is now globally cached per Symbol and refinements, to avoid creating many redundant `CallTargets`.
58+
* Setting and access to the special variables `$~` and `$_` has been refactored to require less splitting.
5859

5960
Changes:
6061

lib/truffle/stringio.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ def gets(sep=$/, limit=Undefined)
315315

316316
# Truffle: $_ is thread and frame local, so we use a primitive to
317317
# set it in the caller's frame.
318-
Primitive.frame_local_variable_set(:$_, getline(false, sep, limit), Primitive.caller_binding)
318+
Primitive.io_last_line_set(Primitive.caller_special_variables, getline(false, sep, limit))
319319
end
320320

321321
def isatty
@@ -346,7 +346,7 @@ def pos=(pos)
346346

347347
def print(*args)
348348
check_writable
349-
args << Primitive.frame_local_variable_get(:$_, Primitive.caller_binding) if args.empty?
349+
args << Primitive.io_last_line_get(Primitive.caller_special_variables) if args.empty?
350350
write((args << $\).flatten.join)
351351
nil
352352
end
@@ -449,7 +449,7 @@ def readline(sep=$/, limit=Undefined)
449449
check_readable
450450
raise EOFError, 'end of file reached' if eof?
451451

452-
Primitive.frame_local_variable_set(:$_, getline(true, sep, limit), Primitive.caller_binding)
452+
Primitive.io_last_line_set(Primitive.caller_special_variables, getline(true, sep, limit))
453453
end
454454

455455
def readlines(sep=$/, limit=Undefined)

lib/truffle/truffle/cext.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,8 +1700,7 @@ def rb_tr_writable(mode)
17001700
end
17011701

17021702
def rb_backref_get
1703-
Primitive.frame_local_variable_get(:$~,
1704-
Truffle::ThreadOperations.ruby_caller([Truffle::CExt, Truffle::Interop.singleton_class]))
1703+
Primitive.regexp_last_match_get(Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]))
17051704
end
17061705

17071706
def rb_gv_set(name, value)
@@ -1719,7 +1718,7 @@ def rb_gv_get(name)
17191718

17201719
def rb_reg_match(re, str)
17211720
result = str ? Truffle::RegexpOperations.match(re, str, 0) : nil
1722-
Primitive.frame_local_variable_set(:$~, result, Truffle::ThreadOperations.ruby_caller([Truffle::CExt, Truffle::Interop.singleton_class]))
1721+
Primitive.regexp_last_match_set(Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]), result)
17231722

17241723
result.begin(0) if result
17251724
end

src/main/java/org/truffleruby/Layouts.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import com.oracle.truffle.api.object.HiddenKey;
1313

14+
import org.truffleruby.parser.TranslatorEnvironment;
15+
1416
public abstract class Layouts {
1517

1618
// Standard identifiers
@@ -23,4 +25,8 @@ public abstract class Layouts {
2325
public static final HiddenKey MARKED_OBJECTS_IDENTIFIER = new HiddenKey("marked_objects"); // Object[]
2426
public static final HiddenKey VALUE_WRAPPER_IDENTIFIER = new HiddenKey("value_wrapper"); // ValueWrapper
2527

28+
// Frame slot name for special variable storage.
29+
30+
public static final String SPECIAL_VARIABLES_STORAGE = TranslatorEnvironment.TEMP_PREFIX + "$~_";
31+
2632
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ public Object send(Object object, String methodName, Object... arguments) {
424424

425425
return IndirectCallNode.getUncached().call(
426426
method.getCallTarget(),
427-
RubyArguments.pack(null, null, method, null, object, null, arguments));
427+
RubyArguments.pack(null, null, null, method, null, object, null, arguments));
428428
}
429429

430430
@TruffleBoundary

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.jcodings.specific.USASCIIEncoding;
2727
import org.jcodings.transcode.EConvFlags;
28+
import org.truffleruby.Layouts;
2829
import org.truffleruby.RubyContext;
2930
import org.truffleruby.RubyLanguage;
3031
import org.truffleruby.SuppressFBWarnings;
@@ -79,6 +80,7 @@
7980
import com.oracle.truffle.api.Truffle;
8081
import com.oracle.truffle.api.TruffleOptions;
8182
import com.oracle.truffle.api.frame.FrameDescriptor;
83+
import com.oracle.truffle.api.frame.FrameSlot;
8284
import com.oracle.truffle.api.object.DynamicObjectLibrary;
8385
import com.oracle.truffle.api.source.Source;
8486
import com.oracle.truffle.api.source.SourceSection;
@@ -226,6 +228,11 @@ public class CoreLibrary {
226228
public final GlobalVariables globalVariables;
227229

228230
public final FrameDescriptor emptyDescriptor;
231+
/* Some things (such as procs created from symbols) require a declaration frame, and this should include a slot for
232+
* special variable storage. This frame descriptor should be used for those frames to provide a constant frame
233+
* descriptor in those cases. */
234+
public final FrameDescriptor emptyDeclarationDescriptor;
235+
public final FrameSlot emptyDeclarationSpecialVariableSlot;
229236

230237
@CompilationFinal private RubyClass eagainWaitReadable;
231238
@CompilationFinal private RubyClass eagainWaitWritable;
@@ -588,6 +595,9 @@ public CoreLibrary(RubyContext context) {
588595

589596
mainObject = new RubyBasicObject(objectClass, language.basicObjectShape);
590597
emptyDescriptor = new FrameDescriptor(Nil.INSTANCE);
598+
emptyDeclarationDescriptor = new FrameDescriptor(Nil.INSTANCE);
599+
emptyDeclarationSpecialVariableSlot = emptyDeclarationDescriptor
600+
.addFrameSlot(Layouts.SPECIAL_VARIABLES_STORAGE);
591601
argv = new RubyArray(arrayClass, RubyLanguage.arrayShape, ArrayStoreLibrary.INITIAL_STORE, 0);
592602

593603
globalVariables = new GlobalVariables();

src/main/java/org/truffleruby/core/binding/BindingNodes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public static MaterializedFrame newFrame(MaterializedFrame parent, FrameDescript
109109
RubyArguments.pack(
110110
parent,
111111
null,
112+
null,
112113
RubyArguments.getMethod(parent),
113114
RubyArguments.getDeclarationContext(parent),
114115
null,

src/main/java/org/truffleruby/core/kernel/KernelNodes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,7 @@ private Object eval(Object target, RootNodeWrapper rootNode, RootCallTarget call
786786
return callNode.call(RubyArguments.pack(
787787
parentFrame,
788788
null,
789+
null,
789790
method,
790791
null,
791792
target,

src/main/java/org/truffleruby/core/kernel/TruffleKernelNodes.java

Lines changed: 174 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,43 @@
2020
import org.truffleruby.builtins.PrimitiveArrayArgumentsNode;
2121
import org.truffleruby.core.basicobject.RubyBasicObject;
2222
import org.truffleruby.core.cast.BooleanCastWithDefaultNodeGen;
23-
import org.truffleruby.core.kernel.TruffleKernelNodesFactory.SetFrameAndThreadLocalVariableFactory;
23+
import org.truffleruby.core.kernel.TruffleKernelNodesFactory.GetSpecialVariableStorageNodeGen;
2424
import org.truffleruby.core.module.ModuleNodes;
2525
import org.truffleruby.core.module.RubyModule;
2626
import org.truffleruby.core.string.RubyString;
2727
import org.truffleruby.core.proc.RubyProc;
2828
import org.truffleruby.core.symbol.RubySymbol;
29-
import org.truffleruby.core.binding.RubyBinding;
29+
import org.truffleruby.language.Nil;
30+
import org.truffleruby.language.RubyContextNode;
3031
import org.truffleruby.language.RubyNode;
3132
import org.truffleruby.language.RubyRootNode;
33+
import org.truffleruby.language.arguments.ReadCallerStorageNode;
34+
import org.truffleruby.language.arguments.RubyArguments;
3235
import org.truffleruby.language.control.RaiseException;
3336
import org.truffleruby.language.dispatch.DispatchNode;
3437
import org.truffleruby.language.globals.ReadSimpleGlobalVariableNode;
3538
import org.truffleruby.language.globals.WriteSimpleGlobalVariableNode;
3639
import org.truffleruby.language.loader.CodeLoader;
3740
import org.truffleruby.language.loader.FileLoader;
3841
import org.truffleruby.language.methods.DeclarationContext;
39-
import org.truffleruby.language.threadlocal.FindThreadAndFrameLocalStorageNode;
40-
import org.truffleruby.language.threadlocal.FindThreadAndFrameLocalStorageNodeGen;
42+
import org.truffleruby.language.threadlocal.SpecialVariableStorage;
4143
import org.truffleruby.parser.ParserContext;
4244
import org.truffleruby.parser.RubySource;
4345

46+
import com.oracle.truffle.api.Assumption;
47+
import com.oracle.truffle.api.CompilerDirectives;
4448
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4549
import com.oracle.truffle.api.dsl.Cached;
4650
import com.oracle.truffle.api.dsl.CreateCast;
4751
import com.oracle.truffle.api.dsl.ImportStatic;
4852
import com.oracle.truffle.api.dsl.NodeChild;
4953
import com.oracle.truffle.api.dsl.Specialization;
54+
import com.oracle.truffle.api.frame.FrameDescriptor;
55+
import com.oracle.truffle.api.frame.FrameSlot;
56+
import com.oracle.truffle.api.frame.FrameSlotKind;
57+
import com.oracle.truffle.api.frame.FrameUtil;
58+
import com.oracle.truffle.api.frame.MaterializedFrame;
59+
import com.oracle.truffle.api.frame.VirtualFrame;
5060
import com.oracle.truffle.api.nodes.IndirectCallNode;
5161
import com.oracle.truffle.api.profiles.ConditionProfile;
5262

@@ -172,37 +182,181 @@ protected Object defineHookedVariableInnerNode(
172182

173183
}
174184

175-
@Primitive(name = "frame_local_variable_get")
176-
public abstract static class GetFrameAndThreadLocalVariable extends PrimitiveArrayArgumentsNode {
185+
@ImportStatic(Layouts.class)
186+
public abstract static class GetSpecialVariableStorage extends RubyContextNode {
187+
188+
public abstract SpecialVariableStorage execute(VirtualFrame frame);
189+
190+
@Specialization(
191+
guards = "frame.getFrameDescriptor() == descriptor",
192+
assumptions = "frameAssumption",
193+
limit = "1")
194+
protected SpecialVariableStorage getFromKnownFrameDescriptor(VirtualFrame frame,
195+
@Cached("frame.getFrameDescriptor()") FrameDescriptor descriptor,
196+
@Cached("declarationDepth(frame)") int declarationFrameDepth,
197+
@Cached("declarationDescriptor(frame, declarationFrameDepth)") FrameDescriptor declarationFrameDescriptor,
198+
@Cached("declarationSlot(declarationFrameDescriptor)") FrameSlot declarationFrameSlot,
199+
@Cached("declarationFrameDescriptor.getVersion()") Assumption frameAssumption) {
200+
Object storage;
201+
if (declarationFrameDepth == 0) {
202+
storage = FrameUtil.getObjectSafe(frame, declarationFrameSlot);
203+
if (storage == nil) {
204+
CompilerDirectives.transferToInterpreterAndInvalidate();
205+
storage = new SpecialVariableStorage();
206+
frame.setObject(declarationFrameSlot, storage);
207+
}
208+
} else {
209+
MaterializedFrame storageFrame = RubyArguments.getDeclarationFrame(frame, declarationFrameDepth);
210+
211+
storage = FrameUtil.getObjectSafe(storageFrame, declarationFrameSlot);
212+
if (storage == nil) {
213+
CompilerDirectives.transferToInterpreterAndInvalidate();
214+
storage = new SpecialVariableStorage();
215+
storageFrame.setObject(declarationFrameSlot, storage);
216+
}
217+
}
218+
return (SpecialVariableStorage) storage;
219+
}
177220

178-
@Child FindThreadAndFrameLocalStorageNode threadLocalNode = FindThreadAndFrameLocalStorageNodeGen.create();
221+
@Specialization(replaces = "getFromKnownFrameDescriptor")
222+
protected SpecialVariableStorage slowPath(VirtualFrame frame) {
223+
return getSlow(frame.materialize());
224+
}
179225

180-
@Specialization
181-
protected Object executeGetValue(RubySymbol name, RubyBinding binding,
182-
@Cached ConditionProfile sameThreadProfile) {
183-
return threadLocalNode.execute(name, binding.getFrame()).get(sameThreadProfile);
226+
@TruffleBoundary
227+
public static SpecialVariableStorage getSlow(MaterializedFrame aFrame) {
228+
MaterializedFrame frame = aFrame;
229+
230+
while (true) {
231+
final FrameSlot slot = getVariableSlot(frame);
232+
if (slot != null) {
233+
Object storage = FrameUtil.getObjectSafe(frame, slot);
234+
if (storage == Nil.INSTANCE) {
235+
storage = new SpecialVariableStorage();
236+
frame.setObject(slot, storage);
237+
}
238+
return (SpecialVariableStorage) storage;
239+
}
240+
241+
final MaterializedFrame nextFrame = RubyArguments.getDeclarationFrame(frame);
242+
if (nextFrame != null) {
243+
frame = nextFrame;
244+
} else {
245+
FrameSlot newSlot = frame
246+
.getFrameDescriptor()
247+
.findOrAddFrameSlot(Layouts.SPECIAL_VARIABLES_STORAGE);
248+
SpecialVariableStorage storage = new SpecialVariableStorage();
249+
frame.setObject(newSlot, storage);
250+
return storage;
251+
}
252+
}
253+
}
254+
255+
protected int declarationDepth(VirtualFrame topFrame) {
256+
MaterializedFrame frame = topFrame.materialize();
257+
int count = 0;
258+
259+
while (true) {
260+
final FrameSlot slot = getVariableSlot(frame);
261+
if (slot != null) {
262+
return count;
263+
}
264+
265+
final MaterializedFrame nextFrame = RubyArguments.getDeclarationFrame(frame);
266+
if (nextFrame != null) {
267+
frame = nextFrame;
268+
count++;
269+
} else {
270+
return count;
271+
}
272+
}
184273
}
185274

275+
protected FrameDescriptor declarationDescriptor(VirtualFrame topFrame, int depth) {
276+
if (depth == 0) {
277+
return topFrame.getFrameDescriptor();
278+
} else {
279+
return RubyArguments.getDeclarationFrame(topFrame, depth).getFrameDescriptor();
280+
}
281+
}
282+
283+
@TruffleBoundary
284+
protected FrameSlot declarationSlot(FrameDescriptor descriptor) {
285+
return descriptor.findOrAddFrameSlot(Layouts.SPECIAL_VARIABLES_STORAGE, FrameSlotKind.Object);
286+
}
287+
288+
private static FrameSlot getVariableSlot(MaterializedFrame frame) {
289+
return frame.getFrameDescriptor().findFrameSlot(Layouts.SPECIAL_VARIABLES_STORAGE);
290+
}
291+
292+
public static GetSpecialVariableStorage create() {
293+
return GetSpecialVariableStorageNodeGen.create();
294+
}
186295
}
187296

188-
@Primitive(name = "frame_local_variable_set")
189-
public abstract static class SetFrameAndThreadLocalVariable extends PrimitiveArrayArgumentsNode {
297+
@Primitive(name = "caller_special_variables")
298+
public abstract static class GetCallerSpecialVariableStorage extends PrimitiveArrayArgumentsNode {
190299

191-
@Child FindThreadAndFrameLocalStorageNode threadLocalNode = FindThreadAndFrameLocalStorageNodeGen.create();
300+
@Child ReadCallerStorageNode callerStorageNode = new ReadCallerStorageNode();
192301

193-
public static SetFrameAndThreadLocalVariable create() {
194-
return SetFrameAndThreadLocalVariableFactory.create(null);
302+
@Specialization
303+
protected Object storage(VirtualFrame frame) {
304+
return callerStorageNode.execute(frame);
195305
}
306+
}
307+
308+
@Primitive(name = "proc_special_variables")
309+
public abstract static class GetProcSpecialVariableStorage extends PrimitiveArrayArgumentsNode {
310+
311+
@Specialization
312+
protected Object storage(VirtualFrame frame, RubyProc proc) {
313+
return proc.declarationStorage;
314+
}
315+
}
316+
317+
@Primitive(name = "regexp_last_match_set")
318+
public abstract static class SetRegexpMatch extends PrimitiveArrayArgumentsNode {
319+
320+
@Specialization
321+
protected Object executeSetRegexpMatch(SpecialVariableStorage storage, Object lastMatch,
322+
@Cached ConditionProfile unsetProfile,
323+
@Cached ConditionProfile sameThreadProfile) {
324+
storage.setLastMatch(lastMatch, getContext(), unsetProfile, sameThreadProfile);
325+
return lastMatch;
326+
}
327+
}
196328

197-
public abstract Object execute(RubySymbol name, Object value, RubyBinding binding);
329+
@Primitive(name = "regexp_last_match_get")
330+
public abstract static class GetRegexpMatch extends PrimitiveArrayArgumentsNode {
198331

199332
@Specialization
200-
protected Object set(RubySymbol name, Object value, RubyBinding binding,
333+
protected Object executeSetRegexpMatch(SpecialVariableStorage storage,
334+
@Cached ConditionProfile unsetProfile,
201335
@Cached ConditionProfile sameThreadProfile) {
202-
threadLocalNode.execute(name, binding.getFrame()).set(value, sameThreadProfile);
203-
return value;
336+
return storage.getLastMatch(unsetProfile, sameThreadProfile);
204337
}
338+
}
339+
340+
@Primitive(name = "io_last_line_set")
341+
public abstract static class SetLastIO extends PrimitiveArrayArgumentsNode {
205342

343+
@Specialization
344+
protected Object executeSetRegexpMatch(SpecialVariableStorage storage, Object lastIO,
345+
@Cached ConditionProfile unsetProfile,
346+
@Cached ConditionProfile sameThreadProfile) {
347+
storage.setLastLine(lastIO, getContext(), unsetProfile, sameThreadProfile);
348+
return lastIO;
349+
}
206350
}
207351

352+
@Primitive(name = "io_last_line_get")
353+
public abstract static class GetLastIO extends PrimitiveArrayArgumentsNode {
354+
355+
@Specialization
356+
protected Object executeSetRegexpMatch(SpecialVariableStorage storage,
357+
@Cached ConditionProfile unsetProfile,
358+
@Cached ConditionProfile sameThreadProfile) {
359+
return storage.getLastLine(unsetProfile, sameThreadProfile);
360+
}
361+
}
208362
}

0 commit comments

Comments
 (0)