Skip to content

Commit e76a460

Browse files
author
Nicolas Laurent
committed
refactor or-assignment nodes by extracting a RunTwiceBranchProfile class
1 parent c35ad71 commit e76a460

File tree

3 files changed

+55
-56
lines changed

3 files changed

+55
-56
lines changed

src/main/java/org/truffleruby/language/constants/OrAssignConstantNode.java

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
package org.truffleruby.language.constants;
1111

1212
import com.oracle.truffle.api.CompilerDirectives;
13-
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
1413
import com.oracle.truffle.api.frame.VirtualFrame;
1514
import com.oracle.truffle.api.profiles.ConditionProfile;
1615
import org.truffleruby.core.cast.BooleanCastNode;
1716
import org.truffleruby.core.cast.BooleanCastNodeGen;
1817
import org.truffleruby.core.module.RubyModule;
1918
import org.truffleruby.language.RubyConstant;
2019
import org.truffleruby.language.RubyContextSourceNode;
20+
import org.truffleruby.utils.RunTwiceBranchProfile;
2121

2222
/** e.g. for {@code module::FOO ||= 1}
2323
*
@@ -31,25 +31,9 @@ public class OrAssignConstantNode extends RubyContextSourceNode {
3131
@Child protected WriteConstantNode writeConstant;
3232
@Child private BooleanCastNode cast;
3333

34-
private enum ExecuteCounter {
35-
NEVER,
36-
ONCE,
37-
MANY;
38-
39-
public ExecuteCounter next() {
40-
if (this == NEVER) {
41-
return ONCE;
42-
} else {
43-
return MANY;
44-
}
45-
}
46-
47-
}
48-
49-
@CompilationFinal private ExecuteCounter executeCounter = ExecuteCounter.NEVER;
5034
private final ConditionProfile triviallyUndefined = ConditionProfile.create();
5135
private final ConditionProfile defined = ConditionProfile.create();
52-
private final ConditionProfile truthy = ConditionProfile.create();
36+
private final RunTwiceBranchProfile writeTwiceProfile = new RunTwiceBranchProfile();
5337

5438
public OrAssignConstantNode(ReadConstantNode readConstant, WriteConstantNode writeConstant) {
5539
this.readConstant = readConstant;
@@ -59,17 +43,11 @@ public OrAssignConstantNode(ReadConstantNode readConstant, WriteConstantNode wri
5943
@Override
6044
public Object execute(VirtualFrame frame) {
6145

62-
if (executeCounter != ExecuteCounter.MANY) {
63-
// Make sure to reset the profiles after the first execution, as we expect the first execution to write
64-
// the constant and subsequent execution to simply read it.
65-
CompilerDirectives.transferToInterpreterAndInvalidate();
66-
executeCounter = executeCounter.next();
67-
}
68-
6946
if (triviallyUndefined.profile(readConstant.isModuleTriviallyUndefined(frame, getLanguage(), getContext()))) {
7047
// It might not be defined because of autoloaded constants (maybe other reasons?),
7148
// simply attempt writing (which will trigger autoloads if required).
7249
// Since we didn't evaluate the module part yet, no side-effects can occur.
50+
writeTwiceProfile.enter();
7351
return writeConstant.execute(frame);
7452
}
7553

@@ -82,12 +60,16 @@ public Object execute(VirtualFrame frame) {
8260

8361
// Next we check if the constant itself is defined, and if it is, we get its value.
8462
final RubyConstant constant = readConstant.getConstantIfDefined(module);
85-
final Object value = defined.profile(constant != null)
63+
64+
final boolean isDefined = defined.profile(constant != null);
65+
66+
final Object value = isDefined
8667
? readConstant.getConstant(module, constant)
8768
: null;
8869

8970
// Write if the constant is undefined or if its value is falsy.
90-
if (!defined.profile(constant != null) || !truthy.profile(castToBoolean(value))) {
71+
if (!isDefined || !castToBoolean(value)) {
72+
writeTwiceProfile.enter();
9173
return writeConstant.execute(frame, module);
9274
} else {
9375
return value;

src/main/java/org/truffleruby/language/control/OrLazyValueDefinedNode.java

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,24 @@
1515
import org.truffleruby.language.RubyContextSourceNode;
1616
import org.truffleruby.language.RubyNode;
1717

18-
import com.oracle.truffle.api.CompilerDirectives;
19-
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
2018
import com.oracle.truffle.api.frame.VirtualFrame;
2119
import com.oracle.truffle.api.profiles.ConditionProfile;
20+
import org.truffleruby.utils.RunTwiceBranchProfile;
2221

2322
/** OrLazyValueDefinedNode is used as the 'or' node for ||=, because we know from idiomatic Ruby usage that this is
2423
* often used to lazy initialize a value. In that case normal counting profiling gives a misleading result. With the RHS
2524
* having been executed once (the lazy initialization) it will be compiled expecting it to be used again. We know that
26-
* it's unlikely to be used again, so only compile it in when it's been used more than once, by using a small saturating
27-
* counter. */
25+
* it's unlikely to be used again, so only compile it in when it's been used more than once, by using a
26+
* {@link RunTwiceBranchProfile}. */
2827
public class OrLazyValueDefinedNode extends RubyContextSourceNode {
2928

3029
@Child private RubyNode left;
3130
@Child private RubyNode right;
3231

3332
@Child private BooleanCastNode leftCast = BooleanCastNode.create();
3433

35-
private enum RightUsage {
36-
NEVER,
37-
ONCE,
38-
MANY;
39-
40-
public RightUsage next() {
41-
if (this == NEVER) {
42-
return ONCE;
43-
} else {
44-
return MANY;
45-
}
46-
}
47-
48-
}
49-
50-
@CompilationFinal private RightUsage rightUsage = RightUsage.NEVER;
51-
52-
private final ConditionProfile conditionProfile = ConditionProfile.createCountingProfile();
34+
private final RunTwiceBranchProfile rightTwiceProfile = new RunTwiceBranchProfile();
35+
private final ConditionProfile countingProfile = ConditionProfile.createCountingProfile();
5336

5437
public OrLazyValueDefinedNode(RubyNode left, RubyNode right) {
5538
this.left = left;
@@ -60,15 +43,10 @@ public OrLazyValueDefinedNode(RubyNode left, RubyNode right) {
6043
public Object execute(VirtualFrame frame) {
6144
final Object leftValue = left.execute(frame);
6245

63-
if (conditionProfile.profile(leftCast.executeToBoolean(leftValue))) {
46+
if (countingProfile.profile(leftCast.executeToBoolean(leftValue))) {
6447
return leftValue;
6548
} else {
66-
if (rightUsage != RightUsage.MANY) {
67-
// Don't compile the RHS of a lazy initialization unless it has been used many times
68-
CompilerDirectives.transferToInterpreterAndInvalidate();
69-
rightUsage = rightUsage.next();
70-
}
71-
49+
rightTwiceProfile.enter();
7250
return right.execute(frame);
7351
}
7452
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.utils;
11+
12+
import com.oracle.truffle.api.CompilerDirectives;
13+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
14+
15+
public final class RunTwiceBranchProfile {
16+
17+
private enum ExecuteCounter {
18+
NEVER,
19+
ONCE,
20+
MANY;
21+
22+
public ExecuteCounter next() {
23+
if (this == NEVER) {
24+
return ONCE;
25+
} else {
26+
return MANY;
27+
}
28+
}
29+
}
30+
31+
@CompilationFinal private ExecuteCounter executeCounter = ExecuteCounter.NEVER;
32+
33+
public void enter() {
34+
if (executeCounter != ExecuteCounter.MANY) {
35+
CompilerDirectives.transferToInterpreterAndInvalidate();
36+
executeCounter = executeCounter.next();
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)