Skip to content

Commit e66e124

Browse files
committed
[GR-33114] Provide a fast path for getting and setting svars from C.
PullRequest: truffleruby/2861
2 parents d3f2159 + 24407a1 commit e66e124

14 files changed

+166
-89
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Performance:
4141
* Improved `String#index` performance by adding a fast path for the `string_character_index` primitive (#2383, @LillianZ).
4242
* Optimized conversion of strings to integers if the string contained a numeric value (#2401, @nirvdrum).
4343
* Use Truffle's `ContextThreadLocal` to speedup access to thread-local data.
44+
* Provide a new fast path for `rb_backref*` and `rb_lastline*`functions from C extensions.
4445

4546
Changes:
4647

lib/truffle/truffle/cext.rb

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,10 +1666,10 @@ def rb_iterate(iteration, iterated_object, callback, callback_arg)
16661666
0, # argc
16671667
nil, # argv
16681668
nil, # blockarg
1669-
], block))
1669+
], Primitive.cext_special_variables_from_stack, block))
16701670
end
16711671
Primitive.cext_unwrap(
1672-
Primitive.call_with_c_mutex_and_frame(iteration, [Primitive.cext_wrap(iterated_object)], wrapped_callback))
1672+
Primitive.call_with_c_mutex_and_frame(iteration, [Primitive.cext_wrap(iterated_object)], Primitive.cext_special_variables_from_stack, wrapped_callback))
16731673
end
16741674

16751675
def rb_thread_wait_fd(fd)
@@ -1733,12 +1733,20 @@ def rb_class_inherited_p(ruby_module, object)
17331733
end
17341734
end
17351735

1736+
def rb_get_special_vars
1737+
vars = Primitive.cext_special_variables_from_stack
1738+
unless vars
1739+
vars = Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class])
1740+
end
1741+
vars
1742+
end
1743+
17361744
def rb_backref_get
1737-
Primitive.regexp_last_match_get(Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]))
1745+
Primitive.regexp_last_match_get(rb_get_special_vars())
17381746
end
17391747

17401748
def rb_backref_set(value)
1741-
Truffle::RegexpOperations::LAST_MATCH_SET.call(value, Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]))
1749+
Truffle::RegexpOperations::LAST_MATCH_SET.call(value, rb_get_special_vars())
17421750
end
17431751

17441752
def rb_gv_set(name, value)
@@ -1756,7 +1764,7 @@ def rb_gv_get(name)
17561764

17571765
def rb_reg_match(re, str)
17581766
result = str ? Truffle::RegexpOperations.match(re, str, 0) : nil
1759-
Primitive.regexp_last_match_set(Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]), result)
1767+
Primitive.regexp_last_match_set(rb_get_special_vars(), result)
17601768

17611769
result.begin(0) if result
17621770
end
@@ -1878,13 +1886,11 @@ def rb_syserr_new(errno, mesg)
18781886
end
18791887

18801888
def rb_lastline_set(str)
1881-
storage = Primitive.ruby_caller_special_variables
1882-
Primitive.io_last_line_set(storage, str)
1889+
Primitive.io_last_line_set(rb_get_special_vars(), str)
18831890
end
18841891

18851892
def rb_lastline_get
1886-
storage = Primitive.ruby_caller_special_variables
1887-
Primitive.io_last_line_get(storage)
1893+
Primitive.io_last_line_get(rb_get_special_vars())
18881894
end
18891895

18901896
def rb_cFiber

lib/truffle/truffle/cext_ruby.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def rb_define_method(mod, name, function, argc)
3838
# We must set block argument if given here so that the
3939
# `rb_block_*` functions will be able to find it by walking the
4040
# stack.
41-
res = Primitive.cext_unwrap(Primitive.call_with_c_mutex_and_frame(function, args, block))
41+
res = Primitive.cext_unwrap(Primitive.call_with_c_mutex_and_frame(function, args, Primitive.caller_special_variables_if_available, block))
4242
Primitive.thread_set_exception(exc)
4343
res
4444
end

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,11 @@ public abstract static class CallWithCExtLockAndFrameNode extends PrimitiveArray
124124
@Child protected CallWithCExtLockNode callCextNode = CallWithCExtLockNodeFactory.create(RubyNode.EMPTY_ARRAY);
125125

126126
@Specialization
127-
protected Object callWithCExtLockAndFrame(Object receiver, RubyArray argsArray, Object block,
127+
protected Object callWithCExtLockAndFrame(
128+
Object receiver, RubyArray argsArray, Object specialVariables, Object block,
128129
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
129130
final ExtensionCallStack extensionStack = getDataNode.execute().getExtensionCallStack();
130-
extensionStack.push(block);
131+
extensionStack.push(specialVariables, block);
131132
try {
132133
return callCextNode.execute(receiver, argsArray);
133134
} finally {
@@ -698,6 +699,16 @@ protected Object block(
698699
}
699700
}
700701

702+
@Primitive(name = "cext_special_variables_from_stack")
703+
public abstract static class VarsFromStackNode extends PrimitiveArrayArgumentsNode {
704+
705+
@Specialization
706+
protected Object variables(
707+
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
708+
return getDataNode.execute().getExtensionCallStack().getVariables();
709+
}
710+
}
711+
701712
@CoreMethod(names = "rb_check_frozen", onSingleton = true, required = 1)
702713
public abstract static class CheckFrozenNode extends CoreMethodArrayArgumentsNode {
703714

@@ -1557,9 +1568,9 @@ protected Object createMarker(RubyDynamicObject object, RubyProc marker) {
15571568
public abstract static class PushPreservingFrame extends CoreMethodArrayArgumentsNode {
15581569

15591570
@Specialization
1560-
protected Object pushFrame(RubyProc block,
1571+
protected Object pushFrame(Object variables, RubyProc block,
15611572
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
1562-
getDataNode.execute().getExtensionCallStack().push(block);
1573+
getDataNode.execute().getExtensionCallStack().push(variables, block);
15631574
return nil;
15641575
}
15651576
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ protected void processReference(RubyContext context, ProcessingReference<?> fina
9393

9494
protected void processReferenceInternal(RubyContext context, FinalizerReference finalizerReference) {
9595
ExtensionCallStack stack = context.getMarkingService().getThreadLocalData().getExtensionCallStack();
96-
stack.push(stack.getBlock());
96+
stack.push(stack.getVariables(), stack.getBlock());
9797
try {
9898
while (!context.isFinalizing()) {
9999
final Finalizer finalizer;

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected void processReference(RubyContext context, ProcessingReference<?> refe
8787
@TruffleBoundary
8888
public void runAllMarkers(RubyContext context) {
8989
ExtensionCallStack stack = markingService.getThreadLocalData().getExtensionCallStack();
90-
stack.push(stack.getBlock());
90+
stack.push(stack.getVariables(), stack.getBlock());
9191
try {
9292
// TODO (eregon, 15 Sept 2020): there seems to be no synchronization here while walking the list of
9393
// markingService, and concurrent mutations seem to be possible.
@@ -117,7 +117,7 @@ public static class MarkerThreadLocalData {
117117
private final ExtensionCallStack extensionCallStack;
118118

119119
public MarkerThreadLocalData(MarkingService service) {
120-
this.extensionCallStack = new ExtensionCallStack(Nil.INSTANCE);
120+
this.extensionCallStack = new ExtensionCallStack(Nil.INSTANCE, Nil.INSTANCE);
121121
}
122122

123123
public ExtensionCallStack getExtensionCallStack() {
@@ -129,18 +129,20 @@ protected static class ExtensionCallStackEntry {
129129
protected final ExtensionCallStackEntry previous;
130130
protected final ArrayList<Object> preservedObjects = new ArrayList<>();
131131
protected final Object block;
132+
protected Object variables;
132133

133-
protected ExtensionCallStackEntry(ExtensionCallStackEntry previous, Object block) {
134+
protected ExtensionCallStackEntry(ExtensionCallStackEntry previous, Object variables, Object block) {
134135
this.previous = previous;
135136
this.block = block;
137+
this.variables = variables;
136138
}
137139
}
138140

139141
public static class ExtensionCallStack {
140142
protected ExtensionCallStackEntry current;
141143

142-
public ExtensionCallStack(Object block) {
143-
current = new ExtensionCallStackEntry(null, block);
144+
public ExtensionCallStack(Object variables, Object block) {
145+
current = new ExtensionCallStackEntry(null, variables, block);
144146
}
145147

146148
public ArrayList<Object> getKeptObjects() {
@@ -151,8 +153,16 @@ public void pop() {
151153
current = current.previous;
152154
}
153155

154-
public void push(Object block) {
155-
current = new ExtensionCallStackEntry(current, block);
156+
public void push(Object variable, Object block) {
157+
current = new ExtensionCallStackEntry(current, variable, block);
158+
}
159+
160+
public Object getVariables() {
161+
return current.variables;
162+
}
163+
164+
public void setVariables(Object variables) {
165+
current.variables = variables;
156166
}
157167

158168
public Object getBlock() {

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

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import org.truffleruby.language.ReadOwnFrameAndVariablesNode;
3838
import org.truffleruby.language.RubyBaseNode;
3939
import org.truffleruby.language.RubyNode;
40-
import org.truffleruby.language.RubyRootNode;
40+
import org.truffleruby.language.arguments.ReadCallerVariablesIfAvailableNode;
4141
import org.truffleruby.language.arguments.ReadCallerVariablesNode;
4242
import org.truffleruby.language.arguments.RubyArguments;
4343
import org.truffleruby.language.control.RaiseException;
@@ -61,17 +61,13 @@
6161
import com.oracle.truffle.api.dsl.NodeChild;
6262
import com.oracle.truffle.api.dsl.Specialization;
6363
import com.oracle.truffle.api.frame.FrameDescriptor;
64-
import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
6564
import com.oracle.truffle.api.frame.FrameSlot;
6665
import com.oracle.truffle.api.frame.FrameSlotKind;
6766
import com.oracle.truffle.api.frame.FrameUtil;
6867
import com.oracle.truffle.api.frame.MaterializedFrame;
6968
import com.oracle.truffle.api.frame.VirtualFrame;
7069
import com.oracle.truffle.api.nodes.IndirectCallNode;
71-
import com.oracle.truffle.api.nodes.Node;
72-
import com.oracle.truffle.api.nodes.RootNode;
7370
import com.oracle.truffle.api.profiles.ConditionProfile;
74-
import com.oracle.truffle.api.Truffle;
7571

7672
@CoreModule("Truffle::KernelOperations")
7773
public abstract class TruffleKernelNodes {
@@ -347,33 +343,20 @@ protected Object storage(VirtualFrame frame) {
347343
}
348344
}
349345

350-
/* When getting special variables from the wrong side of a C call we know it's going to be slow. */
351-
@Primitive(name = "ruby_caller_special_variables")
352-
public abstract static class GetSlowCallerSpecialVariableStorage extends PrimitiveArrayArgumentsNode {
346+
@Primitive(name = "caller_special_variables_if_available")
347+
public abstract static class GetCallerSpecialVariableStorageIfFast extends PrimitiveArrayArgumentsNode {
353348

354-
@Child GetSpecialVariableStorage getStorageNode = GetSpecialVariableStorage.create();
349+
@Child ReadCallerVariablesIfAvailableNode callerVariablesNode = new ReadCallerVariablesIfAvailableNode();
355350

356351
@Specialization
357-
@TruffleBoundary
358-
protected Object storage() {
359-
return getStorageNode.execute(Truffle.getRuntime().iterateFrames(frameInstance -> {
360-
final Node callNode = frameInstance.getCallNode();
361-
362-
if (callNode != null) {
363-
final RootNode rootNode = callNode.getRootNode();
364-
// Skip Ruby frames in cext.rb file since they are implementing methods which are implemented
365-
// with C in MRI, and therefore are also implicitly skipped when when looking up the block passed
366-
// to a C API function.
367-
if (rootNode instanceof RubyRootNode &&
368-
rootNode.getSourceSection().isAvailable() &&
369-
!rootNode.getSourceSection().getSource().getName().endsWith("cext.rb") &&
370-
!rootNode.getSourceSection().getSource().getName().endsWith("cext_ruby.rb")) {
371-
return frameInstance.getFrame(FrameAccess.MATERIALIZE).materialize();
372-
}
373-
}
374-
375-
return null;
376-
}));
352+
protected Object storage(VirtualFrame frame,
353+
@Cached ConditionProfile nullProfile) {
354+
Object variables = callerVariablesNode.execute(frame);
355+
if (nullProfile.profile(variables == null)) {
356+
return nil;
357+
} else {
358+
return variables;
359+
}
377360
}
378361
}
379362

src/main/java/org/truffleruby/core/thread/TruffleThreadNodes.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import org.truffleruby.core.array.RubyArray;
1717
import org.truffleruby.core.array.library.ArrayStoreLibrary;
1818
import org.truffleruby.core.kernel.TruffleKernelNodes.GetSpecialVariableStorage;
19+
import org.truffleruby.core.MarkingServiceNodes;
20+
import org.truffleruby.language.arguments.CallerDataReadingNode;
21+
import org.truffleruby.language.FrameAndVariablesSendingNode;
1922

2023
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2124
import com.oracle.truffle.api.dsl.Cached;
@@ -24,30 +27,52 @@
2427
import com.oracle.truffle.api.frame.Frame;
2528
import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
2629
import com.oracle.truffle.api.library.CachedLibrary;
30+
import com.oracle.truffle.api.nodes.Node;
2731

2832
@CoreModule("Truffle::ThreadOperations")
2933
public class TruffleThreadNodes {
3034

35+
private static class FrameAndCallNode {
36+
37+
public final Frame frame;
38+
public final Node callNode;
39+
40+
public FrameAndCallNode(Frame frame, Node callNode) {
41+
this.frame = frame;
42+
this.callNode = callNode;
43+
}
44+
}
45+
3146
@CoreMethod(names = "ruby_caller_special_variables", onSingleton = true, required = 1)
3247
@ImportStatic(ArrayGuards.class)
33-
public abstract static class FindRubyCallerSpecialStorage extends CoreMethodArrayArgumentsNode {
48+
public abstract static class FindRubyCallerSpecialStorage extends CoreMethodArrayArgumentsNode
49+
implements CallerDataReadingNode {
3450

3551
@TruffleBoundary
3652
@Specialization(limit = "storageStrategyLimit()")
3753
protected Object findRubyCaller(RubyArray modules,
3854
@CachedLibrary("modules.store") ArrayStoreLibrary stores,
39-
@Cached GetSpecialVariableStorage storageNode) {
55+
@Cached GetSpecialVariableStorage storageNode,
56+
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
4057
final int modulesSize = modules.size;
4158
Object[] moduleArray = stores.boxedCopyOfRange(modules.store, 0, modulesSize);
42-
Frame rubyCaller = getContext()
59+
FrameAndCallNode data = getContext()
4360
.getCallStack()
44-
.getCallerFrameNotInModules(FrameAccess.MATERIALIZE, moduleArray);
45-
if (rubyCaller == null) {
61+
.iterateFrameNotInModules(
62+
moduleArray,
63+
f -> new FrameAndCallNode(f.getFrame(FrameAccess.MATERIALIZE), f.getCallNode()));
64+
if (data == null) {
4665
return nil;
4766
} else {
48-
return storageNode.execute(rubyCaller.materialize());
67+
CallerDataReadingNode.notifyCallerToSendData(getContext(), data.callNode, this);
68+
Object variables = storageNode.execute(data.frame.materialize());
69+
getDataNode.execute().getExtensionCallStack().setVariables(variables);
70+
return variables;
4971
}
5072
}
5173

74+
public void startSending(FrameAndVariablesSendingNode node) {
75+
node.startSendingOwnVariables();
76+
}
5277
}
5378
}

src/main/java/org/truffleruby/language/CallStackManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ public Frame getNonJavaCoreCallerFrame(FrameAccess frameAccess) {
7575
}
7676

7777
@TruffleBoundary
78-
public Frame getCallerFrameNotInModules(FrameAccess frameAccess, Object[] modules) {
78+
public <R> R iterateFrameNotInModules(Object[] modules, Function<FrameInstance, R> action) {
7979
final Memo<Boolean> skippedFirstFrameFound = new Memo<>(false);
8080

81-
return getCallerFrame(frameInstance -> {
81+
return iterateFrames(1, frameInstance -> {
8282
final InternalMethod method = tryGetMethod(frameInstance.getFrame(FrameAccess.READ_ONLY));
8383
if (method != null && !ArrayUtils.contains(modules, method.getDeclaringModule())) {
8484
if (skippedFirstFrameFound.get()) {
@@ -87,7 +87,7 @@ public Frame getCallerFrameNotInModules(FrameAccess frameAccess, Object[] module
8787
skippedFirstFrameFound.set(true);
8888
}
8989
return false;
90-
}, frameAccess);
90+
}, action);
9191
}
9292

9393
// Node
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.language.arguments;
11+
12+
import com.oracle.truffle.api.nodes.DirectCallNode;
13+
import com.oracle.truffle.api.nodes.IndirectCallNode;
14+
import com.oracle.truffle.api.nodes.Node;
15+
16+
import org.truffleruby.RubyContext;
17+
import org.truffleruby.language.FrameAndVariablesSendingNode;
18+
import org.truffleruby.language.RubyNode;
19+
20+
public interface CallerDataReadingNode {
21+
public void startSending(FrameAndVariablesSendingNode node);
22+
23+
public static boolean notifyCallerToSendData(RubyContext context, Node callerNode, CallerDataReadingNode reader) {
24+
if (callerNode instanceof DirectCallNode || callerNode instanceof IndirectCallNode) {
25+
Node parent = callerNode.getParent();
26+
while (parent != null) {
27+
if (parent instanceof FrameAndVariablesSendingNode) {
28+
reader.startSending((FrameAndVariablesSendingNode) parent);
29+
return true;
30+
}
31+
if (parent instanceof RubyNode) {
32+
// A node with source info representing Ruby code, we could not find the FrameAndVariablesSendingNode
33+
return false;
34+
}
35+
parent = parent.getParent();
36+
}
37+
}
38+
39+
return false;
40+
}
41+
}

0 commit comments

Comments
 (0)