Skip to content

Commit 21742d9

Browse files
committed
[GR-16505] Fixed the evalBindingAddsVarsCached specialization to also allow blocks.
PullRequest: truffleruby/898
2 parents 316217d + 7627878 commit 21742d9

File tree

4 files changed

+48
-54
lines changed

4 files changed

+48
-54
lines changed

CHANGELOG.md

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

2626
* Interactive sources (like the GraalVM polyglot shell) now all share the same binding (#1695).
2727

28+
Performance:
29+
30+
* `eval(code, binding)` for a fixed `code` containing blocks is now much faster. This improves the performance of rendering `ERB` templates containing loops.
31+
2832
# 20.0.0 beta 1
2933

3034
Bug fixes:

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,18 @@ public static void insertAncestorFrame(RubyContext context, DynamicObject bindin
125125
newFrame(binding, newFrameDescriptor(context));
126126
}
127127

128-
public static boolean hiddenVariable(String name) {
128+
public static boolean isHiddenVariable(Object name) {
129+
if (name instanceof String) {
130+
return isHiddenVariable((String) name);
131+
} else {
132+
return true;
133+
}
134+
}
135+
136+
private static boolean isHiddenVariable(String name) {
129137
assert !name.isEmpty();
130-
return name.startsWith("$") || name.charAt(0) == TranslatorEnvironment.TEMP_PREFIX;
138+
return name.charAt(0) == '$' || // Frame-local global variable
139+
name.charAt(0) == TranslatorEnvironment.TEMP_PREFIX;
131140
}
132141

133142
@CoreMethod(names = { "dup", "clone" })
@@ -158,7 +167,7 @@ public RubyNode coerceToString(RubyNode name) {
158167

159168
@Specialization(guards = {
160169
"name == cachedName",
161-
"!hiddenVariable(cachedName)",
170+
"!isHiddenVariable(cachedName)",
162171
"getFrameDescriptor(binding) == descriptor"
163172
}, limit = "getCacheLimit()")
164173
public boolean localVariableDefinedCached(DynamicObject binding, String name,
@@ -169,13 +178,13 @@ public boolean localVariableDefinedCached(DynamicObject binding, String name,
169178
}
170179

171180
@TruffleBoundary
172-
@Specialization(guards = "!hiddenVariable(name)")
181+
@Specialization(guards = "!isHiddenVariable(name)")
173182
public boolean localVariableDefinedUncached(DynamicObject binding, String name) {
174183
return FindDeclarationVariableNodes.findFrameSlotOrNull(name, getFrame(binding)) != null;
175184
}
176185

177186
@TruffleBoundary
178-
@Specialization(guards = "hiddenVariable(name)")
187+
@Specialization(guards = "isHiddenVariable(name)")
179188
public Object localVariableDefinedLastLine(DynamicObject binding, String name) {
180189
throw new RaiseException(getContext(), coreExceptions().nameError("Bad local variable name", binding, name, this));
181190
}
@@ -197,7 +206,7 @@ public RubyNode coerceToString(RubyNode name) {
197206
return NameToJavaStringNodeGen.create(name);
198207
}
199208

200-
@Specialization(guards = "!hiddenVariable(name)")
209+
@Specialization(guards = "!isHiddenVariable(name)")
201210
public Object localVariableGetUncached(DynamicObject binding, String name,
202211
@Cached("create(null)") FindDeclarationVariableNodes.FindAndReadDeclarationVariableNode readNode) {
203212
MaterializedFrame frame = getFrame(binding);
@@ -209,7 +218,7 @@ public Object localVariableGetUncached(DynamicObject binding, String name,
209218
}
210219

211220
@TruffleBoundary
212-
@Specialization(guards = "hiddenVariable(name)")
221+
@Specialization(guards = "isHiddenVariable(name)")
213222
public Object localVariableGetLastLine(DynamicObject binding, String name) {
214223
throw new RaiseException(getContext(), coreExceptions().nameError("Bad local variable name", binding, name, this));
215224
}
@@ -234,7 +243,7 @@ public RubyNode coerceToString(RubyNode name) {
234243

235244
@Specialization(guards = {
236245
"name == cachedName",
237-
"!hiddenVariable(cachedName)",
246+
"!isHiddenVariable(cachedName)",
238247
"getFrameDescriptor(binding) == cachedFrameDescriptor",
239248
"cachedFrameSlot != null"
240249
}, limit = "getCacheLimit()")
@@ -249,7 +258,7 @@ public Object localVariableSetCached(DynamicObject binding, String name, Object
249258

250259
@Specialization(guards = {
251260
"name == cachedName",
252-
"!hiddenVariable(cachedName)",
261+
"!isHiddenVariable(cachedName)",
253262
"getFrameDescriptor(binding) == cachedFrameDescriptor",
254263
"cachedFrameSlot == null"
255264
}, limit = "getCacheLimit()")
@@ -265,7 +274,7 @@ public Object localVariableSetNewCached(DynamicObject binding, String name, Obje
265274
}
266275

267276
@TruffleBoundary
268-
@Specialization(guards = "!hiddenVariable(name)")
277+
@Specialization(guards = "!isHiddenVariable(name)")
269278
public Object localVariableSetUncached(DynamicObject binding, String name, Object value) {
270279
MaterializedFrame frame = getFrame(binding);
271280
final FrameSlotAndDepth frameSlot = FindDeclarationVariableNodes.findFrameSlotOrNull(name, frame);
@@ -282,7 +291,7 @@ public Object localVariableSetUncached(DynamicObject binding, String name, Objec
282291
}
283292

284293
@TruffleBoundary
285-
@Specialization(guards = "hiddenVariable(name)")
294+
@Specialization(guards = "isHiddenVariable(name)")
286295
public Object localVariableSetLastLine(DynamicObject binding, String name, Object value) {
287296
throw new RaiseException(getContext(), coreExceptions().nameError("Bad local variable name", binding, name, this));
288297
}
@@ -325,8 +334,7 @@ public static DynamicObject listLocalVariables(RubyContext context, Materialized
325334

326335
private static void addNamesFromFrame(RubyContext context, Frame frame, final Set<Object> names) {
327336
for (FrameSlot slot : frame.getFrameDescriptor().getSlots()) {
328-
if (slot.getIdentifier() instanceof String &&
329-
!hiddenVariable((String) slot.getIdentifier())) {
337+
if (!isHiddenVariable(slot.getIdentifier())) {
330338
names.add(context.getSymbolTable().getSymbol((String) slot.getIdentifier()));
331339
}
332340
}

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

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@
128128
import org.truffleruby.language.objects.PropertyFlags;
129129
import org.truffleruby.language.objects.ReadObjectFieldNode;
130130
import org.truffleruby.language.objects.ReadObjectFieldNodeGen;
131-
import org.truffleruby.language.objects.SelfNode;
132131
import org.truffleruby.language.objects.ShapeCachingGuards;
133132
import org.truffleruby.language.objects.SingletonClassNode;
134133
import org.truffleruby.language.objects.TaintNode;
@@ -571,13 +570,16 @@ public RubyRootNode getRootNode() {
571570
}
572571
}
573572

574-
public abstract Object execute(VirtualFrame frame, Object self, DynamicObject str, DynamicObject binding, DynamicObject file, int line);
573+
public abstract Object execute(VirtualFrame frame, Object self, DynamicObject source, DynamicObject binding, DynamicObject file, int line);
574+
575+
// If the source defines new local variables, those should be set in the Binding.
576+
// So we have 2 specializations for whether or not the code defines new local variables.
575577

576578
@Specialization(guards = {
577579
"equalNode.execute(rope(source), cachedSource)",
578580
"equalNode.execute(rope(file), cachedFile)",
579581
"line == cachedLine",
580-
"assignsNoNewVariables(cachedRootNode)",
582+
"!assignsNewUserVariables(getDescriptor(cachedRootNode))",
581583
"bindingDescriptor == getBindingDescriptor(binding)"
582584
}, limit = "getCacheLimit()")
583585
public Object evalBindingNoAddsVarsCached(
@@ -602,8 +604,8 @@ public Object evalBindingNoAddsVarsCached(
602604
"equalNode.execute(rope(source), cachedSource)",
603605
"equalNode.execute(rope(file), cachedFile)",
604606
"line == cachedLine",
605-
"!assignsNoNewVariables(cachedRootNode)",
606-
"assignsNoNewVariables(rootNodeToEval)",
607+
"assignsNewUserVariables(getDescriptor(cachedRootNode))",
608+
"!assignsNewUserVariables(getDescriptor(rootNodeToEval))",
607609
"bindingDescriptor == getBindingDescriptor(binding)"
608610
}, limit = "getCacheLimit()")
609611
public Object evalBindingAddsVarsCached(
@@ -617,13 +619,12 @@ public Object evalBindingAddsVarsCached(
617619
@Cached("line") int cachedLine,
618620
@Cached("getBindingDescriptor(binding)") FrameDescriptor bindingDescriptor,
619621
@Cached("compileSource(cachedSource, getBindingFrame(binding), cachedFile, cachedLine)") RootNodeWrapper cachedRootNode,
620-
@Cached("newBindingDescriptor(getContext(), cachedRootNode)") FrameDescriptor newBindingDescriptor,
622+
@Cached("getDescriptor(cachedRootNode).copy()") FrameDescriptor newBindingDescriptor,
621623
@Cached("compileSource(cachedSource, getBindingFrame(binding), newBindingDescriptor, cachedFile, cachedLine)") RootNodeWrapper rootNodeToEval,
622624
@Cached("createCallTarget(rootNodeToEval)") RootCallTarget cachedCallTarget,
623625
@Cached("create(cachedCallTarget)") DirectCallNode callNode,
624626
@Cached("create()") RopeNodes.EqualNode equalNode) {
625-
final MaterializedFrame parentFrame = BindingNodes.newFrame(binding,
626-
newBindingDescriptor);
627+
final MaterializedFrame parentFrame = BindingNodes.newFrame(binding, newBindingDescriptor);
627628
return eval(self, rootNodeToEval, cachedCallTarget, callNode, parentFrame);
628629
}
629630

@@ -658,7 +659,7 @@ private CodeLoader.DeferredCall doEvalX(Object self, Rope source,
658659
final DeclarationContext declarationContext = RubyArguments.getDeclarationContext(frame);
659660
final FrameDescriptor descriptor = frame.getFrameDescriptor();
660661
RubyRootNode rootNode = buildRootNode(source, frame, file, line, false);
661-
if (!frameHasOnlySelf(descriptor)) {
662+
if (assignsNewUserVariables(descriptor)) {
662663
Layouts.BINDING.setFrame(binding, frame);
663664
}
664665
return getContext().getCodeLoader().prepareExecute(
@@ -687,32 +688,26 @@ protected FrameDescriptor getBindingDescriptor(DynamicObject binding) {
687688
return BindingNodes.getFrameDescriptor(binding);
688689
}
689690

690-
protected FrameDescriptor newBindingDescriptor(RubyContext context, RootNodeWrapper rootNode) {
691-
FrameDescriptor descriptor = rootNode.getRootNode().getFrameDescriptor();
692-
FrameDescriptor newDescriptor = new FrameDescriptor(context.getCoreLibrary().getNil());
693-
for (FrameSlot frameSlot : descriptor.getSlots()) {
694-
newDescriptor.findOrAddFrameSlot(frameSlot.getIdentifier());
695-
}
696-
return newDescriptor;
691+
protected FrameDescriptor getDescriptor(RootNodeWrapper rootNode) {
692+
return rootNode.getRootNode().getFrameDescriptor();
697693
}
698694

699695
protected MaterializedFrame getBindingFrame(DynamicObject binding) {
700696
return BindingNodes.getFrame(binding);
701697
}
702698

703-
protected int getCacheLimit() {
704-
return getContext().getOptions().EVAL_CACHE;
705-
}
706-
707-
protected boolean assignsNoNewVariables(RootNodeWrapper rootNode) {
708-
FrameDescriptor descriptor = rootNode.getRootNode().getFrameDescriptor();
709-
return frameHasOnlySelf(descriptor);
699+
protected static boolean assignsNewUserVariables(FrameDescriptor descriptor) {
700+
for (FrameSlot slot : descriptor.getSlots()) {
701+
if (!BindingNodes.isHiddenVariable(slot.getIdentifier())) {
702+
return true;
703+
}
704+
}
705+
return false;
710706
}
711707

712-
private boolean frameHasOnlySelf(FrameDescriptor descriptor) {
713-
return descriptor.getSize() == 1 && SelfNode.SELF_IDENTIFIER.equals(descriptor.getSlots().get(0).getIdentifier());
708+
protected int getCacheLimit() {
709+
return getContext().getOptions().EVAL_CACHE;
714710
}
715-
716711
}
717712

718713
@CoreMethod(names = "freeze")

src/main/java/org/truffleruby/debug/LexicalScope.java

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import com.oracle.truffle.api.nodes.Node;
2525
import com.oracle.truffle.api.nodes.RootNode;
2626
import org.truffleruby.RubyContext;
27+
import org.truffleruby.core.binding.BindingNodes;
2728
import org.truffleruby.language.arguments.RubyArguments;
28-
import org.truffleruby.parser.TranslatorEnvironment;
2929

3030
import java.util.Collections;
3131
import java.util.HashMap;
@@ -88,20 +88,7 @@ private static Object getArguments(Frame frame) {
8888
}
8989

9090
private static boolean isInternal(FrameSlot slot) {
91-
return isInternal(slot.getIdentifier());
92-
}
93-
94-
private static boolean isInternal(Object identifier) {
95-
if (identifier instanceof String) {
96-
return isInternal((String) identifier);
97-
} else {
98-
return true;
99-
}
100-
}
101-
102-
private static boolean isInternal(String identifier) {
103-
assert !identifier.isEmpty();
104-
return identifier.charAt(0) == TranslatorEnvironment.TEMP_PREFIX;
91+
return BindingNodes.isHiddenVariable(slot.getIdentifier());
10592
}
10693

10794
@ExportLibrary(InteropLibrary.class)

0 commit comments

Comments
 (0)