Skip to content

Commit 9f9b008

Browse files
committed
Provide a fast path for getting and setting backrefs from C.
1 parent a3dcf3d commit 9f9b008

File tree

10 files changed

+141
-21
lines changed

10 files changed

+141
-21
lines changed

lib/truffle/truffle/cext.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,11 +1734,15 @@ def rb_class_inherited_p(ruby_module, object)
17341734
end
17351735

17361736
def rb_backref_get
1737-
Primitive.regexp_last_match_get(Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]))
1737+
vars = Primitive.cext_special_variables_from_stack
1738+
vars = Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]) if vars.nil?
1739+
Primitive.regexp_last_match_get(vars)
17381740
end
17391741

17401742
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]))
1743+
vars = Primitive.cext_special_variables_from_stack
1744+
vars = Truffle::ThreadOperations.ruby_caller_special_variables([Truffle::CExt, Truffle::Interop.singleton_class]) if vars.nil?
1745+
Truffle::RegexpOperations::LAST_MATCH_SET.call(value, vars)
17421746
end
17431747

17441748
def rb_gv_set(name, value)

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_fast, block))
4242
Primitive.thread_set_exception(exc)
4343
res
4444
end

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@ 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(Object receiver, RubyArray argsArray, Object variables, Object block,
128128
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
129129
final ExtensionCallStack extensionStack = getDataNode.execute().getExtensionCallStack();
130-
extensionStack.push(block);
130+
extensionStack.push(variables, block);
131131
try {
132132
return callCextNode.execute(receiver, argsArray);
133133
} finally {
@@ -698,6 +698,16 @@ protected Object block(
698698
}
699699
}
700700

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

@@ -1557,9 +1567,9 @@ protected Object createMarker(RubyDynamicObject object, RubyProc marker) {
15571567
public abstract static class PushPreservingFrame extends CoreMethodArrayArgumentsNode {
15581568

15591569
@Specialization
1560-
protected Object pushFrame(RubyProc block,
1570+
protected Object pushFrame(Object variables, RubyProc block,
15611571
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
1562-
getDataNode.execute().getExtensionCallStack().push(block);
1572+
getDataNode.execute().getExtensionCallStack().push(variables, block);
15631573
return nil;
15641574
}
15651575
}

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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.truffleruby.language.RubyBaseNode;
3939
import org.truffleruby.language.RubyNode;
4040
import org.truffleruby.language.RubyRootNode;
41+
import org.truffleruby.language.arguments.MaybeReadCallerVariablesNode;
4142
import org.truffleruby.language.arguments.ReadCallerVariablesNode;
4243
import org.truffleruby.language.arguments.RubyArguments;
4344
import org.truffleruby.language.control.RaiseException;
@@ -346,6 +347,23 @@ protected Object storage(VirtualFrame frame) {
346347
}
347348
}
348349

350+
@Primitive(name = "caller_special_variables_if_fast")
351+
public abstract static class GetCallerSpecialVariableStorageIfFast extends PrimitiveArrayArgumentsNode {
352+
353+
@Child MaybeReadCallerVariablesNode callerVariablesNode = new MaybeReadCallerVariablesNode();
354+
355+
@Specialization
356+
protected Object storage(VirtualFrame frame,
357+
@Cached ConditionProfile nullProfile) {
358+
Object variables = callerVariablesNode.execute(frame);
359+
if (nullProfile.profile(variables == null)) {
360+
return nil;
361+
} else {
362+
return variables;
363+
}
364+
}
365+
}
366+
349367
/* When getting special variables from the wrong side of a C call we know it's going to be slow. */
350368
@Primitive(name = "ruby_caller_special_variables")
351369
public abstract static class GetSlowCallerSpecialVariableStorage extends PrimitiveArrayArgumentsNode {

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

Lines changed: 45 additions & 5 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.FrameAndVariablesSendingNode;
21+
import org.truffleruby.language.RubyContextNode;
1922

2023
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2124
import com.oracle.truffle.api.dsl.Cached;
@@ -24,10 +27,24 @@
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.DirectCallNode;
31+
import com.oracle.truffle.api.nodes.IndirectCallNode;
32+
import com.oracle.truffle.api.nodes.Node;
2733

2834
@CoreModule("Truffle::ThreadOperations")
2935
public class TruffleThreadNodes {
3036

37+
private static class FrameAndCallNode {
38+
39+
public final Frame frame;
40+
public final Node callNode;
41+
42+
public FrameAndCallNode(Frame frame, Node callNode) {
43+
this.frame = frame;
44+
this.callNode = callNode;
45+
}
46+
}
47+
3148
@CoreMethod(names = "ruby_caller_special_variables", onSingleton = true, required = 1)
3249
@ImportStatic(ArrayGuards.class)
3350
public abstract static class FindRubyCallerSpecialStorage extends CoreMethodArrayArgumentsNode {
@@ -36,18 +53,41 @@ public abstract static class FindRubyCallerSpecialStorage extends CoreMethodArra
3653
@Specialization(limit = "storageStrategyLimit()")
3754
protected Object findRubyCaller(RubyArray modules,
3855
@CachedLibrary("modules.store") ArrayStoreLibrary stores,
39-
@Cached GetSpecialVariableStorage storageNode) {
56+
@Cached GetSpecialVariableStorage storageNode,
57+
@Cached MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
4058
final int modulesSize = modules.size;
4159
Object[] moduleArray = stores.boxedCopyOfRange(modules.store, 0, modulesSize);
42-
Frame rubyCaller = getContext()
60+
FrameAndCallNode data = getContext()
4361
.getCallStack()
44-
.getCallerFrameNotInModules(FrameAccess.MATERIALIZE, moduleArray);
45-
if (rubyCaller == null) {
62+
.iterateFrameNotInModules(
63+
moduleArray,
64+
f -> new FrameAndCallNode(f.getFrame(FrameAccess.MATERIALIZE), f.getCallNode()));
65+
if (data == null) {
4666
return nil;
4767
} else {
48-
return storageNode.execute(rubyCaller.materialize());
68+
notifyToStartSendingStorage(data.callNode);
69+
Object variables = storageNode.execute(data.frame.materialize());
70+
getDataNode.execute().getExtensionCallStack().setVariables(variables);
71+
return variables;
4972
}
5073
}
5174

75+
private static boolean notifyToStartSendingStorage(Node callerNode) {
76+
if (callerNode instanceof DirectCallNode || callerNode instanceof IndirectCallNode) {
77+
Node parent = callerNode.getParent();
78+
while (parent != null) {
79+
if (parent instanceof FrameAndVariablesSendingNode) {
80+
((FrameAndVariablesSendingNode) parent).startSendingOwnVariables();
81+
return true;
82+
}
83+
if (parent instanceof RubyContextNode) {
84+
return false;
85+
}
86+
parent = parent.getParent();
87+
}
88+
}
89+
90+
return false;
91+
}
5292
}
5393
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ public Frame getCallerFrameNotInModules(FrameAccess frameAccess, Object[] module
9090
}, frameAccess);
9191
}
9292

93+
@TruffleBoundary
94+
public <R> R iterateFrameNotInModules(Object[] modules, Function<FrameInstance, R> action) {
95+
final Memo<Boolean> skippedFirstFrameFound = new Memo<>(false);
96+
97+
return iterateFrames(1, frameInstance -> {
98+
final InternalMethod method = tryGetMethod(frameInstance.getFrame(FrameAccess.READ_ONLY));
99+
if (method != null && !ArrayUtils.contains(modules, method.getDeclaringModule())) {
100+
if (skippedFirstFrameFound.get()) {
101+
return true;
102+
}
103+
skippedFirstFrameFound.set(true);
104+
}
105+
return false;
106+
}, action);
107+
}
108+
93109
// Node
94110

95111
@TruffleBoundary
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2015, 2020 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+
public class MaybeReadCallerVariablesNode extends ReadCallerVariablesNode {
13+
14+
public static MaybeReadCallerVariablesNode create() {
15+
return new MaybeReadCallerVariablesNode();
16+
}
17+
18+
@Override
19+
protected Object getCallerData() {
20+
return null;
21+
}
22+
}

src/main/java/org/truffleruby/language/arguments/ReadCallerDataNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public Object execute(Frame frame) {
4545
protected abstract Object getData(Frame frame);
4646

4747
@TruffleBoundary
48-
private Object getCallerData() {
48+
protected Object getCallerData() {
4949
if (!notifyCallerToSendData()) {
5050
// If we fail to notify the call node (e.g., because it is a UncachedDispatchNode which is not handled yet),
5151
// we don't want to deoptimize this CallTarget on every call.

0 commit comments

Comments
 (0)