Skip to content

Commit 091f3a4

Browse files
committed
Always split core methods taking a block
1 parent c1435d4 commit 091f3a4

File tree

21 files changed

+69
-27
lines changed

21 files changed

+69
-27
lines changed

lib/mri/monitor.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def mon_owned?
217217
def mon_synchronize(&block)
218218
Primitive.monitor_synchronize(@mon_mutex, block)
219219
end
220+
Truffle::Graal.always_split instance_method(:mon_synchronize)
220221
alias synchronize mon_synchronize
221222

222223
#

lib/truffle/truffle/cext.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
require_relative 'cext_constants'
1313
require_relative 'cext_structs'
1414

15+
# TODO (eregon, 1 Sep 2022): should we always_split or use separate CallTargets for usages of Primitive.call_with* in this file?
16+
1517
module Truffle::CExt
1618
DATA_TYPE = Primitive.object_hidden_var_create :data_type
1719
DATA_HOLDER = Primitive.object_hidden_var_create :data_holder
@@ -978,6 +980,7 @@ def rb_hash_foreach(hash, func, farg)
978980
end
979981
end
980982
end
983+
Truffle::Graal.always_split instance_method(:rb_hash_foreach)
981984

982985
def rb_path_to_class(path)
983986
begin
@@ -1046,6 +1049,7 @@ def rb_protect(function, arg, write_status, status)
10461049
Truffle::Interop.execute_without_conversion(write_status, status, pos)
10471050
res
10481051
end
1052+
Truffle::Graal.always_split instance_method(:rb_protect)
10491053

10501054
def rb_jump_tag(pos)
10511055
if pos > 0
@@ -1064,10 +1068,12 @@ def rb_jump_tag(pos)
10641068
def rb_yield(value)
10651069
Primitive.interop_execute(rb_block_proc, [value])
10661070
end
1071+
Truffle::Graal.always_split instance_method(:rb_yield)
10671072

10681073
def rb_yield_splat(values)
10691074
Primitive.interop_execute(rb_block_proc, values)
10701075
end
1076+
Truffle::Graal.always_split instance_method(:rb_yield_splat)
10711077

10721078
def rb_ivar_lookup(object, name, default_value)
10731079
# TODO CS 24-Jul-16 races - needs a new primitive or be defined in Java?
@@ -1396,6 +1402,7 @@ def rb_mutex_synchronize(mutex, func, arg)
13961402
Primitive.cext_unwrap(Primitive.interop_execute(func, [Primitive.cext_wrap(arg)]))
13971403
end
13981404
end
1405+
Truffle::Graal.always_split instance_method(:rb_mutex_synchronize)
13991406

14001407
def rb_gc_enable
14011408
GC.enable
@@ -1547,6 +1554,7 @@ def rb_ensure(b_proc, data1, e_proc, data2)
15471554
Primitive.interop_execute(e_proc, [data2])
15481555
end
15491556
end
1557+
Truffle::Graal.always_split instance_method(:rb_ensure)
15501558

15511559
def rb_rescue(b_proc, data1, r_proc, data2)
15521560
begin
@@ -1559,6 +1567,7 @@ def rb_rescue(b_proc, data1, r_proc, data2)
15591567
end
15601568
end
15611569
end
1570+
Truffle::Graal.always_split instance_method(:rb_rescue)
15621571

15631572
def rb_rescue2(b_proc, data1, r_proc, data2, rescued)
15641573
begin
@@ -1567,6 +1576,7 @@ def rb_rescue2(b_proc, data1, r_proc, data2, rescued)
15671576
Primitive.interop_execute(r_proc, [data2, Primitive.cext_wrap(e)])
15681577
end
15691578
end
1579+
Truffle::Graal.always_split instance_method(:rb_rescue2)
15701580

15711581
def rb_exec_recursive(func, obj, arg)
15721582
result = nil
@@ -1581,6 +1591,7 @@ def rb_exec_recursive(func, obj, arg)
15811591
result
15821592
end
15831593
end
1594+
Truffle::Graal.always_split instance_method(:rb_exec_recursive)
15841595

15851596
def rb_catch_obj(tag, func, data)
15861597
catch tag do |caught|

src/annotations/java/org/truffleruby/builtins/CoreMethod.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
* RubyHash, and the arguments descriptor will tell if it was kwargs or positional, as always. */
5252
boolean rest() default false;
5353

54+
/** Passes the optional block as an argument to the node. Also causes {@link Split#DEFAULT} to be resolved to
55+
* {@link Split#ALWAYS}, unless {@link #split()} is set to a non-default value, since if a block is passed and the
56+
* block is called it is necessary to split for the best performance. Splitting eagerly instead of lazily via the
57+
* heuristic avoids an extra copy and works better with {@code RubyRootNode#getParentFrameDescriptor()} when the
58+
* possible loop in this core method does many iterations on the first method call as it then preserves the
59+
* single-caller chain from the beginning, instead of breaking it until split and executed again for the first and
60+
* second call sites. {@link Split#NEVER} should be used if this method never calls the block but just stores it. */
5461
boolean needsBlock() default false;
5562

5663
/** Try to lower argument <code>i</code> (starting at 0) to an int if its value is a long. The 0 is reserved for
@@ -73,6 +80,6 @@
7380
/** Use these names in Ruby core methods stubs, ignore argument names in Java specializations. */
7481
String[] argumentNames() default {};
7582

76-
Split split() default Split.HEURISTIC;
83+
Split split() default Split.DEFAULT;
7784

7885
}

src/annotations/java/org/truffleruby/language/methods/Split.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
package org.truffleruby.language.methods;
1111

1212
public enum Split {
13+
/** Special value for the CoreMethod annotation, resolved to either HEURISTIC or ALWAYS, based on needsBlock */
14+
DEFAULT,
1315
ALWAYS,
1416
HEURISTIC,
1517
/** Disallow splitting for this CallTarget, which avoids making a eager uninitialized copy of the AST. Useful

src/main/java/org/truffleruby/builtins/CoreMethodNodeManager.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ private RubyClass getSingletonClass(Object object) {
121121
return SingletonClassNode.getUncached().executeSingletonClass(object);
122122
}
123123

124+
private Split effectiveSplit(Split split, boolean needsBlock) {
125+
if (context.getOptions().CORE_ALWAYS_CLONE) {
126+
return Split.ALWAYS;
127+
} else if (split == Split.DEFAULT) {
128+
return needsBlock ? Split.ALWAYS : Split.HEURISTIC;
129+
} else {
130+
return split;
131+
}
132+
}
133+
124134
private void addCoreMethod(RubyModule module, MethodDetails methodDetails) {
125135
final CoreMethod annotation = methodDetails.getMethodAnnotation();
126136

@@ -134,7 +144,7 @@ private void addCoreMethod(RubyModule module, MethodDetails methodDetails) {
134144
final NodeFactory<? extends RubyBaseNode> nodeFactory = methodDetails.getNodeFactory();
135145
final boolean onSingleton = annotation.onSingleton() || annotation.constructor();
136146
final boolean isModuleFunc = annotation.isModuleFunction();
137-
final Split split = context.getOptions().CORE_ALWAYS_CLONE ? Split.ALWAYS : annotation.split();
147+
final Split split = effectiveSplit(annotation.split(), annotation.needsBlock());
138148

139149
final Function<SharedMethodInfo, RootCallTarget> callTargetFactory = sharedMethodInfo -> {
140150
return createCoreMethodRootNode(nodeFactory, language, sharedMethodInfo, split, annotation);
@@ -164,6 +174,7 @@ public void addLazyCoreMethod(
164174
int required,
165175
int optional,
166176
boolean rest,
177+
boolean needsBlock,
167178
String... names) {
168179

169180
final RubyModule module = getModule(moduleName, isClass);
@@ -172,7 +183,7 @@ public void addLazyCoreMethod(
172183
if (alwaysInlined) {
173184
finalSplit = Split.NEVER;
174185
} else {
175-
finalSplit = context.getOptions().CORE_ALWAYS_CLONE ? Split.ALWAYS : split;
186+
finalSplit = effectiveSplit(split, needsBlock);
176187
}
177188

178189
final Function<SharedMethodInfo, RootCallTarget> callTargetFactory = sharedMethodInfo -> {
@@ -329,7 +340,7 @@ public RootCallTarget createCoreMethodRootNode(NodeFactory<? extends RubyBaseNod
329340

330341
if (method.lowerFixnum().length > 0 || method.raiseIfFrozenSelf() || method.raiseIfNotMutableSelf() ||
331342
method.returnsEnumeratorIfNoBlock() || !method.enumeratorSize().isEmpty() ||
332-
method.split() != Split.HEURISTIC) {
343+
method.split() != Split.DEFAULT) {
333344
throw new Error("Always-inlined methods do not support all @CoreMethod attributes for " + nodeClass);
334345
}
335346

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,6 @@ protected RubyArray concatManyGeneral(RubyArray array, Object first, Object[] re
662662

663663
@CoreMethod(names = "delete", required = 1, needsBlock = true)
664664
@ImportStatic(ArrayGuards.class)
665-
@ReportPolymorphism
666665
public abstract static class DeleteNode extends YieldingCoreMethodNode {
667666

668667
@Child private SameOrEqualNode sameOrEqualNode = SameOrEqualNode.create();
@@ -1005,7 +1004,6 @@ protected Object eqlNotArray(RubyArray a, Object b) {
10051004
}
10061005

10071006
@CoreMethod(names = "fill", rest = true, needsBlock = true, raiseIfFrozenSelf = true)
1008-
@ReportPolymorphism
10091007
public abstract static class FillNode extends ArrayCoreMethodNode {
10101008

10111009
@Specialization(
@@ -1331,21 +1329,18 @@ protected RubyArray initializeCopy(RubyArray self, RubyArray from,
13311329

13321330
@Primitive(name = "array_inject")
13331331
@ImportStatic(ArrayGuards.class)
1334-
@ReportPolymorphism
13351332
public abstract static class InjectNode extends YieldingCoreMethodNode {
13361333

13371334
@Child private DispatchNode dispatch = DispatchNode.create(PUBLIC);
13381335

13391336
// Uses block
13401337

13411338
@Specialization(guards = { "isEmptyArray(array)", "wasProvided(initialOrSymbol)" })
1342-
@ReportPolymorphism.Exclude
13431339
protected Object injectEmptyArray(RubyArray array, Object initialOrSymbol, NotProvided symbol, RubyProc block) {
13441340
return initialOrSymbol;
13451341
}
13461342

13471343
@Specialization(guards = { "isEmptyArray(array)" })
1348-
@ReportPolymorphism.Exclude
13491344
protected Object injectEmptyArrayNoInitial(
13501345
RubyArray array, NotProvided initialOrSymbol, NotProvided symbol, RubyProc block) {
13511346
return nil;
@@ -1484,7 +1479,6 @@ public Object injectSymbolHelper(VirtualFrame frame, RubyArray array, String sym
14841479

14851480
@CoreMethod(names = { "map", "collect" }, needsBlock = true, enumeratorSize = "size")
14861481
@ImportStatic(ArrayGuards.class)
1487-
@ReportPolymorphism
14881482
public abstract static class MapNode extends YieldingCoreMethodNode {
14891483

14901484
@Specialization(limit = "storageStrategyLimit()")
@@ -1757,7 +1751,6 @@ protected RubyArray pushMany(VirtualFrame frame, RubyArray array, Object value,
17571751

17581752
@CoreMethod(names = "reject", needsBlock = true, enumeratorSize = "size")
17591753
@ImportStatic(ArrayGuards.class)
1760-
@ReportPolymorphism
17611754
public abstract static class RejectNode extends YieldingCoreMethodNode {
17621755

17631756
@Specialization(limit = "storageStrategyLimit()")
@@ -1792,7 +1785,6 @@ protected Object reject(RubyArray array, RubyProc block,
17921785

17931786
@CoreMethod(names = "reject!", needsBlock = true, enumeratorSize = "size", raiseIfFrozenSelf = true)
17941787
@ImportStatic(ArrayGuards.class)
1795-
@ReportPolymorphism
17961788
public abstract static class RejectInPlaceNode extends YieldingCoreMethodNode {
17971789

17981790
@Child private BooleanCastNode booleanCastNode = BooleanCastNode.create();
@@ -2048,7 +2040,6 @@ private void reverse(ArrayStoreLibrary stores,
20482040

20492041
@CoreMethod(names = { "select", "filter" }, needsBlock = true, enumeratorSize = "size")
20502042
@ImportStatic(ArrayGuards.class)
2051-
@ReportPolymorphism
20522043
public abstract static class SelectNode extends YieldingCoreMethodNode {
20532044

20542045
@Specialization(limit = "storageStrategyLimit()")
@@ -2159,11 +2150,9 @@ protected int size(RubyArray array,
21592150

21602151
@CoreMethod(names = "sort", needsBlock = true)
21612152
@ImportStatic(ArrayGuards.class)
2162-
@ReportPolymorphism
21632153
public abstract static class SortNode extends ArrayCoreMethodNode {
21642154

21652155
@Specialization(guards = "isEmptyArray(array)")
2166-
@ReportPolymorphism.Exclude
21672156
protected RubyArray sortEmpty(RubyArray array, Object unusedBlock) {
21682157
return createEmptyArray();
21692158
}

src/main/java/org/truffleruby/core/basicobject/BasicObjectNodes.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.truffleruby.language.methods.DeclarationContext.SingletonClassOfSelfDefaultDefinee;
5757
import org.truffleruby.language.methods.InternalMethod;
5858
import org.truffleruby.language.methods.LookupMethodNode;
59+
import org.truffleruby.language.methods.Split;
5960
import org.truffleruby.language.objects.AllocationTracing;
6061
import org.truffleruby.language.objects.MetaClassNode;
6162
import org.truffleruby.language.objects.ObjectIDOperations;
@@ -465,7 +466,8 @@ protected Object instanceExec(Object receiver, Object[] arguments, Nil block) {
465466

466467
}
467468

468-
@CoreMethod(names = "method_missing", needsBlock = true, rest = true, optional = 1, visibility = Visibility.PRIVATE)
469+
@CoreMethod(names = "method_missing", needsBlock = true, rest = true, optional = 1, visibility = Visibility.PRIVATE,
470+
split = Split.NEVER)
469471
public abstract static class MethodMissingNode extends CoreMethodArrayArgumentsNode {
470472

471473
@Specialization

src/main/java/org/truffleruby/core/hash/HashNodes.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.truffleruby.language.Visibility;
4040
import org.truffleruby.language.control.RaiseException;
4141
import org.truffleruby.language.dispatch.DispatchNode;
42+
import org.truffleruby.language.methods.Split;
4243
import org.truffleruby.language.objects.AllocationTracing;
4344
import org.truffleruby.language.objects.shared.PropagateSharingNode;
4445
import org.truffleruby.language.yield.CallBlockNode;
@@ -362,7 +363,8 @@ protected boolean isEmpty(RubyHash hash) {
362363
}
363364
}
364365

365-
@CoreMethod(names = "initialize", needsBlock = true, optional = 1, raiseIfFrozenSelf = true)
366+
@CoreMethod(names = "initialize", needsBlock = true, optional = 1, raiseIfFrozenSelf = true,
367+
split = Split.HEURISTIC)
366368
@ImportStatic(HashGuards.class)
367369
public abstract static class InitializeNode extends CoreMethodArrayArgumentsNode {
368370

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
import org.truffleruby.language.locals.FindDeclarationVariableNodes.FindAndReadDeclarationVariableNode;
122122
import org.truffleruby.language.methods.GetMethodObjectNode;
123123
import org.truffleruby.language.methods.InternalMethod;
124+
import org.truffleruby.language.methods.Split;
124125
import org.truffleruby.language.objects.AllocationTracing;
125126
import org.truffleruby.language.objects.CheckIVarNameNode;
126127
import org.truffleruby.language.objects.IsANode;
@@ -1153,7 +1154,7 @@ protected boolean isATypeError(Object self, Object module) {
11531154

11541155
}
11551156

1156-
@CoreMethod(names = "lambda", isModuleFunction = true, needsBlock = true)
1157+
@CoreMethod(names = "lambda", isModuleFunction = true, needsBlock = true, split = Split.HEURISTIC)
11571158
public abstract static class LambdaNode extends CoreMethodArrayArgumentsNode {
11581159

11591160
@Specialization
@@ -1322,7 +1323,7 @@ protected RubyArray privateMethods(Object self, boolean includeAncestors) {
13221323

13231324
}
13241325

1325-
@CoreMethod(names = "proc", isModuleFunction = true, needsBlock = true)
1326+
@CoreMethod(names = "proc", isModuleFunction = true, needsBlock = true, split = Split.HEURISTIC)
13261327
public abstract static class ProcNode extends CoreMethodArrayArgumentsNode {
13271328

13281329
@Specialization

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.truffleruby.language.loader.FileLoader;
5353
import org.truffleruby.language.locals.FindDeclarationVariableNodes;
5454
import org.truffleruby.language.methods.DeclarationContext;
55+
import org.truffleruby.language.methods.Split;
5556
import org.truffleruby.language.threadlocal.SpecialVariableStorage;
5657
import org.truffleruby.parser.ParserContext;
5758
import org.truffleruby.parser.RubySource;
@@ -72,7 +73,7 @@
7273
@CoreModule("Truffle::KernelOperations")
7374
public abstract class TruffleKernelNodes {
7475

75-
@CoreMethod(names = "at_exit", onSingleton = true, needsBlock = true, required = 1)
76+
@CoreMethod(names = "at_exit", onSingleton = true, needsBlock = true, required = 1, split = Split.NEVER)
7677
public abstract static class AtExitSystemNode extends CoreMethodArrayArgumentsNode {
7778

7879
@TruffleBoundary

0 commit comments

Comments
 (0)