Skip to content

Commit 3967426

Browse files
committed
Move core method assumptions to RubyLanguage
1 parent d0bdb29 commit 3967426

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+378
-333
lines changed

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,7 @@ public void initialize() {
236236
Metrics.printTime("after-load-nodes");
237237

238238
// Capture known builtin methods
239-
240-
coreMethods = new CoreMethods(this);
239+
coreMethods = new CoreMethods(language, this);
241240

242241
// Load the part of the core library defined in Ruby
243242

src/main/java/org/truffleruby/RubyLanguage.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.truffleruby.core.fiber.RubyFiber;
3131
import org.truffleruby.core.hash.RubyHash;
3232
import org.graalvm.options.OptionValues;
33+
import org.truffleruby.core.inlined.CoreMethodAssumptions;
3334
import org.truffleruby.core.kernel.TraceManager;
3435
import org.truffleruby.core.klass.RubyClass;
3536
import org.truffleruby.core.method.RubyMethod;
@@ -139,7 +140,9 @@ public class RubyLanguage extends TruffleLanguage<RubyContext> {
139140
public final Assumption singleContextAssumption = Truffle
140141
.getRuntime()
141142
.createAssumption("single RubyContext per RubyLanguage instance");
143+
public final CyclicAssumption traceFuncUnusedAssumption = new CyclicAssumption("set_trace_func is not used");
142144

145+
public final CoreMethodAssumptions coreMethodAssumptions;
143146
public final CoreStrings coreStrings;
144147
public final CoreSymbols coreSymbols;
145148
public final RopeCache ropeCache;
@@ -199,6 +202,7 @@ public class RubyLanguage extends TruffleLanguage<RubyContext> {
199202
public static final Shape weakMapShape = createShape(RubyWeakMap.class);
200203

201204
public RubyLanguage() {
205+
coreMethodAssumptions = new CoreMethodAssumptions(this);
202206
coreStrings = new CoreStrings(this);
203207
coreSymbols = new CoreSymbols();
204208
ropeCache = new RopeCache(coreSymbols);

src/main/java/org/truffleruby/core/array/ArrayNodes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,8 +2176,8 @@ protected RubyArray sortVeryShort(VirtualFrame frame, RubyArray array, NotProvid
21762176
"!isSmall(array)",
21772177
"stores.isPrimitive(array.store)" },
21782178
assumptions = {
2179-
"getContext().getCoreMethods().integerCmpAssumption",
2180-
"getContext().getCoreMethods().floatCmpAssumption" },
2179+
"getContext().getLanguageSlow().coreMethodAssumptions.integerCmpAssumption",
2180+
"getContext().getLanguageSlow().coreMethodAssumptions.floatCmpAssumption" },
21812181
limit = "storageStrategyLimit()")
21822182
protected Object sortPrimitiveArrayNoBlock(RubyArray array, NotProvided block,
21832183
@CachedLibrary("array.store") ArrayStoreLibrary stores,

src/main/java/org/truffleruby/core/cast/ToProcNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ protected RubyProc doRubyProc(RubyProc proc) {
4646
// No need to guard the refinements here since refinements are always the same in a given source location
4747
@Specialization(
4848
guards = "symbol == cachedSymbol",
49-
assumptions = "getContext().getCoreMethods().symbolToProcAssumption",
49+
assumptions = "getContext().getLanguageSlow().coreMethodAssumptions.symbolToProcAssumption",
5050
limit = "1")
5151
protected Object doRubySymbolASTInlined(VirtualFrame frame, RubySymbol symbol,
5252
@Cached("symbol") RubySymbol cachedSymbol,

src/main/java/org/truffleruby/core/inlined/BinaryInlinedOperationNode.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
package org.truffleruby.core.inlined;
1111

12-
import org.truffleruby.RubyContext;
12+
import org.truffleruby.RubyLanguage;
1313
import org.truffleruby.language.RubyNode;
1414
import org.truffleruby.language.dispatch.RubyCallNodeParameters;
1515

@@ -21,10 +21,10 @@
2121
public abstract class BinaryInlinedOperationNode extends InlinedOperationNode {
2222

2323
public BinaryInlinedOperationNode(
24-
RubyContext context,
24+
RubyLanguage language,
2525
RubyCallNodeParameters callNodeParameters,
2626
Assumption... assumptions) {
27-
super(context, callNodeParameters, assumptions);
27+
super(language, callNodeParameters, assumptions);
2828
}
2929

3030
protected abstract RubyNode getLeft();
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* Copyright (c) 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.core.inlined;
11+
12+
import com.oracle.truffle.api.Assumption;
13+
import com.oracle.truffle.api.Truffle;
14+
import org.truffleruby.RubyLanguage;
15+
import org.truffleruby.core.CoreLibrary;
16+
import org.truffleruby.core.klass.RubyClass;
17+
import org.truffleruby.core.module.ModuleFields;
18+
import org.truffleruby.language.RubyNode;
19+
import org.truffleruby.language.dispatch.RubyCallNode;
20+
import org.truffleruby.language.dispatch.RubyCallNodeParameters;
21+
import org.truffleruby.language.methods.BlockDefinitionNode;
22+
import org.truffleruby.parser.TranslatorEnvironment;
23+
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
import java.util.function.Consumer;
27+
28+
/** We inline basic operations directly in the AST (instead of a method call) as it makes little sense to compile them
29+
* in isolation without the surrounding method and it delays more interesting compilations by filling the compilation
30+
* queue. The performance in interpreter is typically also improved, making inlined basic operations an optimization
31+
* useful mostly for warmup. The choice of inlining a basic operation is based on running benchmarks and observing which
32+
* basic operations methods are compiled early. AST inlining is also useful to guarantee splitting for small operations
33+
* which make sense to always split.
34+
* <p>
35+
* Each inlined basic operation has its own Node to conveniently express guards and assumptions. Inlined basic
36+
* operations execute in the caller frame, which may be useful for some operations accessing the caller frame like
37+
* Kernel#block_given?.
38+
* <p>
39+
* IMPORTANT: The specialization guards must ensure no exception (e.g.: division by 0) can happen during the inlined
40+
* operation (and therefore no nested Ruby call as that could raise an exception), as that would make the inlined
41+
* operation NOT appear in the backtrace which would be incorrect and confusing. Inlined nodes should use as few nodes
42+
* as possible to save on footprint. In trivial cases it is better to inline the logic directly (e.g. for Integer#==
43+
* with two int's).
44+
* <p>
45+
* Two strategies are used to check method re-definition.
46+
* <li>If the class is a leaf class (there cannot be instances of a subclass of that class), then we only need to check
47+
* the receiver is an instance of that class and register an Assumption for the given method name (see
48+
* {@link ModuleFields#registerAssumption(String, com.oracle.truffle.api.Assumption)}). In such cases the method must be
49+
* public as we do not check visibility.</li>
50+
* <li>Otherwise, we need to do a method lookup and verify the method that would be called is the standard definition we
51+
* expect.</li>
52+
* <p>
53+
* Every specialization should use {@code assumptions = "assumptions",} to check at least the tracing Assumption. When
54+
* adding a new node, it is a good idea to add a debug print in the non-inlined method and try calling it with and
55+
* without a {@code set_trace_func proc{};} before to see if the inlined version is used correctly. */
56+
public class CoreMethodAssumptions {
57+
58+
final RubyLanguage language;
59+
60+
final Assumption integerNegAssumption, floatNegAssumption;
61+
final Assumption integerAddAssumption, floatAddAssumption;
62+
final Assumption integerSubAssumption, floatSubAssumption;
63+
final Assumption integerMulAssumption, floatMulAssumption;
64+
final Assumption integerDivAssumption, floatDivAssumption;
65+
final Assumption integerModAssumption, floatModAssumption;
66+
public final Assumption integerCmpAssumption, floatCmpAssumption;
67+
68+
final Assumption integerLeftShiftAssumption;
69+
final Assumption integerRightShiftAssumption;
70+
final Assumption integerBitOrAssumption;
71+
final Assumption integerBitAndAssumption;
72+
73+
final Assumption integerEqualAssumption;
74+
final Assumption integerCaseEqualAssumption;
75+
final Assumption integerLessThanAssumption, integerLessOrEqualAssumption;
76+
final Assumption integerGreaterThanAssumption, integerGreaterOrEqualAssumption;
77+
78+
final Assumption nilClassIsNilAssumption;
79+
80+
public final Assumption symbolToProcAssumption;
81+
82+
private final List<Consumer<CoreLibrary>> classAssumptionsToRegister;
83+
84+
public CoreMethodAssumptions(RubyLanguage language) {
85+
this.language = language;
86+
this.classAssumptionsToRegister = new ArrayList<>();
87+
88+
integerNegAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "-@");
89+
floatNegAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "-@");
90+
91+
integerAddAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "+");
92+
floatAddAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "+");
93+
94+
integerSubAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "-");
95+
floatSubAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "-");
96+
97+
integerMulAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "*");
98+
floatMulAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "*");
99+
100+
integerDivAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "/");
101+
floatDivAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "/");
102+
103+
integerModAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "%");
104+
floatModAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "%");
105+
106+
integerCmpAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "<=>");
107+
floatCmpAssumption = registerAssumption((cl) -> cl.floatClass, "Float", "<=>");
108+
109+
integerLeftShiftAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "<<");
110+
integerRightShiftAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", ">>");
111+
integerBitOrAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "|");
112+
integerBitAndAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "&");
113+
114+
integerEqualAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "==");
115+
integerCaseEqualAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "===");
116+
integerLessThanAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "<");
117+
integerLessOrEqualAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", "<=");
118+
integerGreaterThanAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", ">");
119+
integerGreaterOrEqualAssumption = registerAssumption((cl) -> cl.integerClass, "Integer", ">=");
120+
121+
nilClassIsNilAssumption = registerAssumption((cl) -> cl.nilClass, "Nil", "nil?");
122+
123+
symbolToProcAssumption = registerAssumption((cl) -> cl.symbolClass, "Symbol", "to_proc");
124+
125+
}
126+
127+
@FunctionalInterface
128+
public interface ContextGetClass {
129+
RubyClass apply(CoreLibrary c);
130+
}
131+
132+
private Assumption registerAssumption(ContextGetClass classGetter, String className, String methodName) {
133+
final Assumption assumption = Truffle.getRuntime().createAssumption("inlined " + className + "#" + methodName);
134+
classAssumptionsToRegister.add((cl) -> classGetter.apply(cl).fields.registerAssumption(methodName, assumption));
135+
return assumption;
136+
}
137+
138+
public void registerAssumptions(CoreLibrary coreLibrary) {
139+
for (Consumer<CoreLibrary> registerers : classAssumptionsToRegister) {
140+
registerers.accept(coreLibrary);
141+
}
142+
}
143+
144+
public RubyNode createCallNode(RubyCallNodeParameters callParameters, TranslatorEnvironment environment) {
145+
if (!language.options.BASICOPS_INLINE || callParameters.isSplatted() || callParameters.isSafeNavigation()) {
146+
return new RubyCallNode(callParameters);
147+
}
148+
149+
final RubyNode self = callParameters.getReceiver();
150+
final RubyNode[] args = callParameters.getArguments();
151+
int n = 1 /* self */ + args.length;
152+
153+
if (callParameters.getBlock() != null) {
154+
if (callParameters.getMethodName().equals("lambda") && callParameters.isIgnoreVisibility() &&
155+
n == 1 && callParameters.getBlock() instanceof BlockDefinitionNode) {
156+
return InlinedLambdaNodeGen.create(language, callParameters, self, callParameters.getBlock());
157+
} else {
158+
// The calls below should all not be given a block
159+
return new RubyCallNode(callParameters);
160+
}
161+
}
162+
163+
if (n == 1) {
164+
switch (callParameters.getMethodName()) {
165+
case "!":
166+
return InlinedNotNodeGen.create(language, callParameters, self);
167+
case "-@":
168+
return InlinedNegNodeGen.create(language, callParameters, self);
169+
case "binding":
170+
if (callParameters.isIgnoreVisibility()) {
171+
return InlinedBindingNodeGen.create(language, callParameters, self);
172+
}
173+
break;
174+
case "block_given?":
175+
if (callParameters.isIgnoreVisibility()) {
176+
return InlinedBlockGivenNodeGen.create(language, callParameters, environment, self);
177+
}
178+
break;
179+
case "nil?":
180+
return InlinedIsNilNodeGen.create(language, callParameters, self);
181+
case "bytesize":
182+
return InlinedByteSizeNodeGen.create(language, callParameters, self);
183+
default:
184+
}
185+
} else if (n == 2) {
186+
switch (callParameters.getMethodName()) {
187+
case "+":
188+
return InlinedAddNodeGen.create(language, callParameters, self, args[0]);
189+
case "-":
190+
return InlinedSubNodeGen.create(language, callParameters, self, args[0]);
191+
case "*":
192+
return InlinedMulNodeGen.create(language, callParameters, self, args[0]);
193+
case "/":
194+
return InlinedDivNodeGen.create(language, callParameters, self, args[0]);
195+
case "%":
196+
return InlinedModNodeGen.create(language, callParameters, self, args[0]);
197+
case "<<":
198+
return InlinedLeftShiftNodeGen.create(language, callParameters, self, args[0]);
199+
case ">>":
200+
return InlinedRightShiftNodeGen.create(language, callParameters, self, args[0]);
201+
case "&":
202+
return InlinedBitAndNodeGen.create(language, callParameters, self, args[0]);
203+
case "|":
204+
return InlinedBitOrNodeGen.create(language, callParameters, self, args[0]);
205+
case "==":
206+
return InlinedEqualNodeGen.create(language, callParameters, self, args[0]);
207+
case "===":
208+
return InlinedCaseEqualNodeGen.create(language, callParameters, self, args[0]);
209+
case "<":
210+
return InlinedLessThanNodeGen.create(language, callParameters, self, args[0]);
211+
case "<=":
212+
return InlinedLessOrEqualNodeGen.create(language, callParameters, self, args[0]);
213+
case ">":
214+
return InlinedGreaterThanNodeGen.create(language, callParameters, self, args[0]);
215+
case ">=":
216+
return InlinedGreaterOrEqualNodeGen.create(language, callParameters, self, args[0]);
217+
case "[]":
218+
return InlinedIndexGetNodeGen.create(language, callParameters, self, args[0]);
219+
case "at":
220+
return InlinedAtNodeGen.create(language, callParameters, self, args[0]);
221+
case "is_a?":
222+
return InlinedIsANodeGen.create(language, callParameters, self, args[0]);
223+
case "kind_of?":
224+
return InlinedKindOfNodeGen.create(language, callParameters, self, args[0]);
225+
default:
226+
}
227+
} else if (n == 3) {
228+
switch (callParameters.getMethodName()) {
229+
case "[]=":
230+
return InlinedIndexSetNodeGen.create(language, callParameters, self, args[0], args[1]);
231+
default:
232+
}
233+
}
234+
235+
return new RubyCallNode(callParameters);
236+
}
237+
238+
239+
}

0 commit comments

Comments
 (0)