Skip to content

Commit 4e5dd65

Browse files
committed
Fixed the evalBindingAddsVarsCached specialization to also allow blocks
* Centralize checks for hidden variables in BindingNodes. * Use FrameDescriptor.copy() instead of doing it manually.
1 parent 316217d commit 4e5dd65

File tree

4 files changed

+31
-38
lines changed

4 files changed

+31
-38
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,14 @@ public static void insertAncestorFrame(RubyContext context, DynamicObject bindin
125125
newFrame(binding, newFrameDescriptor(context));
126126
}
127127

128+
public static boolean hiddenVariable(Object name) {
129+
if (name instanceof String) {
130+
return hiddenVariable((String) name);
131+
} else {
132+
return true;
133+
}
134+
}
135+
128136
public static boolean hiddenVariable(String name) {
129137
assert !name.isEmpty();
130138
return name.startsWith("$") || name.charAt(0) == TranslatorEnvironment.TEMP_PREFIX;

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

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ public RubyRootNode getRootNode() {
577577
"equalNode.execute(rope(source), cachedSource)",
578578
"equalNode.execute(rope(file), cachedFile)",
579579
"line == cachedLine",
580-
"assignsNoNewVariables(cachedRootNode)",
580+
"!assignsNewUserVariables(getDescriptor(cachedRootNode))",
581581
"bindingDescriptor == getBindingDescriptor(binding)"
582582
}, limit = "getCacheLimit()")
583583
public Object evalBindingNoAddsVarsCached(
@@ -602,8 +602,8 @@ public Object evalBindingNoAddsVarsCached(
602602
"equalNode.execute(rope(source), cachedSource)",
603603
"equalNode.execute(rope(file), cachedFile)",
604604
"line == cachedLine",
605-
"!assignsNoNewVariables(cachedRootNode)",
606-
"assignsNoNewVariables(rootNodeToEval)",
605+
"assignsNewUserVariables(getDescriptor(cachedRootNode))",
606+
"!assignsNewUserVariables(getDescriptor(rootNodeToEval))",
607607
"bindingDescriptor == getBindingDescriptor(binding)"
608608
}, limit = "getCacheLimit()")
609609
public Object evalBindingAddsVarsCached(
@@ -617,13 +617,12 @@ public Object evalBindingAddsVarsCached(
617617
@Cached("line") int cachedLine,
618618
@Cached("getBindingDescriptor(binding)") FrameDescriptor bindingDescriptor,
619619
@Cached("compileSource(cachedSource, getBindingFrame(binding), cachedFile, cachedLine)") RootNodeWrapper cachedRootNode,
620-
@Cached("newBindingDescriptor(getContext(), cachedRootNode)") FrameDescriptor newBindingDescriptor,
620+
@Cached("getDescriptor(cachedRootNode).copy()") FrameDescriptor newBindingDescriptor,
621621
@Cached("compileSource(cachedSource, getBindingFrame(binding), newBindingDescriptor, cachedFile, cachedLine)") RootNodeWrapper rootNodeToEval,
622622
@Cached("createCallTarget(rootNodeToEval)") RootCallTarget cachedCallTarget,
623623
@Cached("create(cachedCallTarget)") DirectCallNode callNode,
624624
@Cached("create()") RopeNodes.EqualNode equalNode) {
625-
final MaterializedFrame parentFrame = BindingNodes.newFrame(binding,
626-
newBindingDescriptor);
625+
final MaterializedFrame parentFrame = BindingNodes.newFrame(binding, newBindingDescriptor);
627626
return eval(self, rootNodeToEval, cachedCallTarget, callNode, parentFrame);
628627
}
629628

@@ -658,7 +657,7 @@ private CodeLoader.DeferredCall doEvalX(Object self, Rope source,
658657
final DeclarationContext declarationContext = RubyArguments.getDeclarationContext(frame);
659658
final FrameDescriptor descriptor = frame.getFrameDescriptor();
660659
RubyRootNode rootNode = buildRootNode(source, frame, file, line, false);
661-
if (!frameHasOnlySelf(descriptor)) {
660+
if (assignsNewUserVariables(descriptor)) {
662661
Layouts.BINDING.setFrame(binding, frame);
663662
}
664663
return getContext().getCodeLoader().prepareExecute(
@@ -687,32 +686,26 @@ protected FrameDescriptor getBindingDescriptor(DynamicObject binding) {
687686
return BindingNodes.getFrameDescriptor(binding);
688687
}
689688

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;
689+
protected FrameDescriptor getDescriptor(RootNodeWrapper rootNode) {
690+
return rootNode.getRootNode().getFrameDescriptor();
697691
}
698692

699693
protected MaterializedFrame getBindingFrame(DynamicObject binding) {
700694
return BindingNodes.getFrame(binding);
701695
}
702696

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);
697+
protected static boolean assignsNewUserVariables(FrameDescriptor descriptor) {
698+
for (FrameSlot slot : descriptor.getSlots()) {
699+
if (!BindingNodes.hiddenVariable(slot.getIdentifier())) {
700+
return true;
701+
}
702+
}
703+
return false;
710704
}
711705

712-
private boolean frameHasOnlySelf(FrameDescriptor descriptor) {
713-
return descriptor.getSize() == 1 && SelfNode.SELF_IDENTIFIER.equals(descriptor.getSlots().get(0).getIdentifier());
706+
protected int getCacheLimit() {
707+
return getContext().getOptions().EVAL_CACHE;
714708
}
715-
716709
}
717710

718711
@CoreMethod(names = "freeze")

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
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;
2829
import org.truffleruby.parser.TranslatorEnvironment;
2930

@@ -88,20 +89,7 @@ private static Object getArguments(Frame frame) {
8889
}
8990

9091
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;
92+
return BindingNodes.hiddenVariable(slot.getIdentifier());
10593
}
10694

10795
@ExportLibrary(InteropLibrary.class)

0 commit comments

Comments
 (0)