Skip to content

Commit a5fd6c8

Browse files
committed
[GR-19220] Basics of building a static keyword arguments descriptor at call-sites (#2637)
PullRequest: truffleruby/3267
2 parents 21f9032 + 3d219bd commit a5fd6c8

File tree

12 files changed

+173
-46
lines changed

12 files changed

+173
-46
lines changed

spec/truffle/arguments_descriptor_spec.rb

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def descriptor(*)
3838
splat = [1, 2]
3939
descriptor(*splat).should == []
4040

41-
descriptor(a: 1).should == [:keywords]
41+
descriptor(a: 1).should == [:keywords, :a]
4242
kw = { b: 2 }
4343
descriptor(**kw).should == [:keywords]
4444
empty_kw = {}
@@ -64,7 +64,7 @@ def no_kws(a, b)
6464
it "contain keyword arguments" do
6565
info = only_kws(a: 1, b: 2)
6666
info.values.should == [1, 2]
67-
info.descriptor.should == [:keywords] if truffleruby?
67+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
6868
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
6969
end
7070

@@ -75,12 +75,12 @@ def only_kws_and_opt(a:, b: 101)
7575

7676
info = only_kws_and_opt(a: 1, b: 2)
7777
info.values.should == [1, 2]
78-
info.descriptor.should == [:keywords] if truffleruby?
78+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
7979
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
8080

8181
info = only_kws_and_opt(a: 1)
8282
info.values.should == [1, 101]
83-
info.descriptor.should == [:keywords] if truffleruby?
83+
info.descriptor.should == [:keywords, :a] if truffleruby?
8484
info.arguments.should == [{a: 1}] if truffleruby?
8585
end
8686

@@ -91,7 +91,7 @@ def posn_kws(a, b:)
9191

9292
info = posn_kws(1, b: 2)
9393
info.values.should == [1, 2]
94-
info.descriptor.should == [:keywords] if truffleruby?
94+
info.descriptor.should == [:keywords, :b] if truffleruby?
9595
info.arguments.should == [1, {b: 2}] if truffleruby?
9696
end
9797

@@ -102,34 +102,34 @@ def posn_kws_defaults(a, b: 101, c: 102)
102102
it "contain optional keyword arguments with positional arguments" do
103103
info = posn_kws_defaults(1, b: 2, c: 3)
104104
info.values.should == [1, 2, 3]
105-
info.descriptor.should == [:keywords] if truffleruby?
105+
info.descriptor.should == [:keywords, :b, :c] if truffleruby?
106106
info.arguments.should == [1, {b: 2, c: 3}] if truffleruby?
107107
end
108108

109109
it "do not contain missing optional keyword arguments" do
110110
info = posn_kws_defaults(1, b: 2)
111111
info.values.should == [1, 2, 102]
112-
info.descriptor.should == [:keywords] if truffleruby?
112+
info.descriptor.should == [:keywords, :b] if truffleruby?
113113
info.arguments.should == [1, {b: 2}] if truffleruby?
114114

115115
info = posn_kws_defaults(1, c: 3)
116116
info.values.should == [1, 101, 3]
117-
info.descriptor.should == [:keywords] if truffleruby?
117+
info.descriptor.should == [:keywords, :c] if truffleruby?
118118
info.arguments.should == [1, {c: 3}] if truffleruby?
119119
end
120120

121121
it "do not contain the name of distant explicitly splatted keyword arguments" do
122122
distant = {b: 2}
123123
info = only_kws(a: 1, **distant)
124124
info.values.should == [1, 2]
125-
info.descriptor.should == [:keywords] if truffleruby?
125+
info.descriptor.should == [:keywords, :a] if truffleruby?
126126
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
127127
end
128128

129129
it "do not contain the name of near explicitly splatted keyword arguments" do
130130
info = only_kws(a: 1, **{b: 2})
131131
info.values.should == [1, 2]
132-
info.descriptor.should == [:keywords] if truffleruby?
132+
info.descriptor.should == [:keywords, :a] if truffleruby?
133133
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
134134
end
135135

@@ -140,7 +140,7 @@ def opt_and_kws(a, b=2, c: nil)
140140

141141
info = opt_and_kws(1, c: 3)
142142
info.values.should == [1, 2, 3]
143-
info.descriptor.should == [:keywords] if truffleruby?
143+
info.descriptor.should == [:keywords, :c] if truffleruby?
144144
info.arguments.should == [1, {c: 3}] if truffleruby?
145145
end
146146

@@ -197,7 +197,7 @@ def struct_new_like(a, *b, c: 101)
197197

198198
info = struct_new_like('A', :a, :b, c: 1)
199199
info.values.should == ['A', [:a, :b], 1]
200-
info.descriptor.should == [:keywords] if truffleruby?
200+
info.descriptor.should == [:keywords, :c] if truffleruby?
201201
info.arguments.should == ['A', :a, :b, {c: 1}] if truffleruby?
202202

203203
info = struct_new_like('A', :a, :b, {c: 1})
@@ -240,7 +240,7 @@ def describe_like(a, b = nil)
240240

241241
info = describe_like(1, b: 2)
242242
info.values.should == [1, {b: 2}]
243-
info.descriptor.should == [:keywords] if truffleruby?
243+
info.descriptor.should == [:keywords, :b] if truffleruby?
244244
info.arguments.should == [1, {b: 2}] if truffleruby?
245245
end
246246

@@ -262,7 +262,7 @@ def rest(*a)
262262

263263
info = rest(a: 1, b: 2)
264264
info.values.should == [[{a: 1, b: 2}]]
265-
info.descriptor.should == [:keywords] if truffleruby?
265+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
266266
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
267267
end
268268

@@ -273,7 +273,7 @@ def kw_rest(**a)
273273

274274
info = kw_rest(a: 1, b: 2)
275275
info.values.should == [{a: 1, b: 2}]
276-
info.descriptor.should == [:keywords] if truffleruby?
276+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
277277
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
278278
end
279279

@@ -284,17 +284,17 @@ def kw_and_kw_rest(a:, **b)
284284

285285
info = kw_and_kw_rest(a: 1)
286286
info.values.should == [1, {}]
287-
info.descriptor.should == [:keywords] if truffleruby?
287+
info.descriptor.should == [:keywords, :a] if truffleruby?
288288
info.arguments.should == [{a: 1}] if truffleruby?
289289

290290
info = kw_and_kw_rest(a: 1, b: 2, c: 3)
291291
info.values.should == [1, {b: 2, c: 3}]
292-
info.descriptor.should == [:keywords] if truffleruby?
292+
info.descriptor.should == [:keywords, :a, :b, :c] if truffleruby?
293293
info.arguments.should == [{a: 1, b: 2, c: 3}] if truffleruby?
294294

295295
info = kw_and_kw_rest("abc" => 123, a: 1, b: 2)
296296
info.values.should == [1, {"abc" => 123, b: 2}]
297-
info.descriptor.should == [:keywords] if truffleruby?
297+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
298298
info.arguments.should == [{"abc" => 123, a: 1, b: 2}] if truffleruby?
299299
end
300300

@@ -310,7 +310,7 @@ def mixture(a, b = nil, c = nil, d, e: nil, **f)
310310

311311
info = mixture(1, 2, e: 3)
312312
info.values.should == [1, nil, nil, 2, 3, {}]
313-
info.descriptor.should == [:keywords] if truffleruby?
313+
info.descriptor.should == [:keywords, :e] if truffleruby?
314314
info.arguments.should == [1, 2, {e: 3}] if truffleruby?
315315

316316
info = mixture(1, 2, {foo: :bar})
@@ -345,7 +345,7 @@ def self.new(a:, b:)
345345

346346
info = new_kw.new(a: 1, b: 2)
347347
info.values.should == [1, 2]
348-
info.descriptor.should == [:keywords] if truffleruby?
348+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
349349
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
350350
end
351351

@@ -358,11 +358,26 @@ def self.new(*a)
358358

359359
info = new_rest.new(a: 1, b: 2)
360360
info.values.should == [[{a: 1, b: 2}]]
361+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
362+
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
363+
end
364+
365+
it "work through delegation, but by reifying the keyword argument hash" do
366+
def delegator(...)
367+
delegated(...)
368+
end
369+
370+
def delegated(a:, b:)
371+
ArgumentsDescriptorSpecs::Info.new([a, b], Primitive.arguments_descriptor, Primitive.arguments)
372+
end
373+
374+
info = delegator(a: 1, b: 2)
375+
info.values.should == [1, 2]
361376
info.descriptor.should == [:keywords] if truffleruby?
362377
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
363378
end
364379

365-
it "work through implicit super" do
380+
it "work through implicit super, but by reifying the keyword argument hash" do
366381
parent = Class.new do
367382
def implicit_super(a:, b:)
368383
ArgumentsDescriptorSpecs::Info.new([a, b], Primitive.arguments_descriptor, Primitive.arguments)
@@ -396,7 +411,7 @@ def explicit_super(a:, b:)
396411

397412
info = child.new.explicit_super(a: 1, b: 2)
398413
info.values.should == [1, 2]
399-
info.descriptor.should == [:keywords] if truffleruby?
414+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
400415
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
401416
end
402417

@@ -413,7 +428,7 @@ def use_yielder(x, y)
413428

414429
info = use_yielder(1, 2)
415430
info.values.should == [1, 2]
416-
info.descriptor.should == [:keywords] if truffleruby?
431+
info.descriptor.should == [:keywords, :a, :b] if truffleruby?
417432
info.arguments.should == [{a: 1, b: 2}] if truffleruby?
418433
end
419434

@@ -422,7 +437,7 @@ def use_yielder(x, y)
422437
info([args, kwargs], Primitive.arguments_descriptor, Primitive.arguments)
423438
end
424439
info.values.should == [[], {a: 1}]
425-
info.descriptor.should == [:keywords] if truffleruby?
440+
info.descriptor.should == [:keywords, :a] if truffleruby?
426441
info.arguments.should == [{a: 1}] if truffleruby?
427442
end
428443

@@ -431,7 +446,7 @@ def use_yielder(x, y)
431446
ArgumentsDescriptorSpecs::Info.new([args, kwargs], Primitive.arguments_descriptor, Primitive.arguments)
432447
end
433448
info.values.should == [[], {a: 1}]
434-
info.descriptor.should == [:keywords] if truffleruby?
449+
info.descriptor.should == [:keywords, :a] if truffleruby?
435450
info.arguments.should == [{a: 1}] if truffleruby?
436451
end
437452

@@ -440,11 +455,11 @@ def use_yielder(x, y)
440455
ArgumentsDescriptorSpecs::Info.new([args, kwargs], Primitive.arguments_descriptor, Primitive.arguments)
441456
end.call(a: 1)
442457
info.values.should == [[], {a: 1}]
443-
info.descriptor.should == [:keywords] if truffleruby?
458+
info.descriptor.should == [:keywords, :a] if truffleruby?
444459
info.arguments.should == [{a: 1}] if truffleruby?
445460
end
446461

447-
it "work through Thread#new" do
462+
it "work through Thread#new, but by reifying the keyword argument hash" do
448463
info = Thread.new(a: 1) do |*args, **kwargs|
449464
ArgumentsDescriptorSpecs::Info.new([args, kwargs], Primitive.arguments_descriptor, Primitive.arguments)
450465
end.value
@@ -458,7 +473,7 @@ def use_yielder(x, y)
458473
ArgumentsDescriptorSpecs::Info.new([args, kwargs], Primitive.arguments_descriptor, Primitive.arguments)
459474
end.resume(a: 1)
460475
info.values.should == [[], {a: 1}]
461-
info.descriptor.should == [:keywords] if truffleruby?
476+
info.descriptor.should == [:keywords, :a] if truffleruby?
462477
info.arguments.should == [{a: 1}] if truffleruby?
463478

464479
Fiber.new(&-> { true }).resume(**{}).should == true

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
import org.truffleruby.language.RubyEvalInteractiveRootNode;
106106
import org.truffleruby.language.RubyInlineParsingRequestNode;
107107
import org.truffleruby.language.RubyParsingRequestNode;
108+
import org.truffleruby.language.arguments.KeywordArgumentsDescriptorManager;
108109
import org.truffleruby.language.objects.RubyObjectType;
109110
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;
110111
import org.truffleruby.language.threadlocal.SpecialVariableStorage;
@@ -209,6 +210,7 @@ public static final class ThreadLocalState {
209210
public final RopeCache ropeCache;
210211
public final RegexpTable regexpTable;
211212
public final SymbolTable symbolTable;
213+
public final KeywordArgumentsDescriptorManager keywordArgumentsDescriptorManager = new KeywordArgumentsDescriptorManager();
212214
public final FrozenStringLiterals frozenStringLiterals;
213215

214216
public final ReferenceQueue<Object> sharedReferenceQueue = new ReferenceQueue<>();

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
import org.truffleruby.language.arguments.ArgumentsDescriptor;
9191
import org.truffleruby.language.arguments.EmptyArgumentsDescriptor;
9292
import org.truffleruby.language.arguments.KeywordArgumentsDescriptor;
93+
import org.truffleruby.language.arguments.KeywordArgumentsDescriptorManager;
9394
import org.truffleruby.language.arguments.RubyArguments;
9495
import org.truffleruby.language.backtrace.Backtrace;
9596
import org.truffleruby.language.constants.GetConstantNode;
@@ -319,7 +320,8 @@ protected Object sendWithoutCExtLock(
319320
return sendWithoutCExtLock(frame, receiver, method, block, EmptyArgumentsDescriptor.INSTANCE, args,
320321
dispatchNode, ownedProfile);
321322
} else {
322-
return sendWithoutCExtLock(frame, receiver, method, block, KeywordArgumentsDescriptor.INSTANCE, args,
323+
return sendWithoutCExtLock(frame, receiver, method, block,
324+
KeywordArgumentsDescriptorManager.EMPTY, args,
323325
dispatchNode, ownedProfile);
324326
}
325327
}

src/main/java/org/truffleruby/core/VMPrimitiveNodes.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
*/
3838
package org.truffleruby.core;
3939

40+
import java.util.Arrays;
4041
import java.util.Map.Entry;
42+
import java.util.stream.Stream;
4143

4244
import com.oracle.truffle.api.TruffleStackTrace;
4345
import com.oracle.truffle.api.interop.InteropLibrary;
@@ -611,7 +613,14 @@ private RubyArray descriptorToArray(ArgumentsDescriptor descriptor) {
611613
if (descriptor == EmptyArgumentsDescriptor.INSTANCE) {
612614
return createEmptyArray();
613615
} else if (descriptor instanceof KeywordArgumentsDescriptor) {
614-
return createArray(new Object[]{ getLanguage().getSymbol("keywords") });
616+
final KeywordArgumentsDescriptor keywordArgumentsDescriptor = (KeywordArgumentsDescriptor) descriptor;
617+
618+
final Stream<RubySymbol> keywords = Stream.concat(
619+
Stream.of("keywords"),
620+
Arrays.stream(keywordArgumentsDescriptor.getKeywords()))
621+
.map(getLanguage()::getSymbol);
622+
623+
return createArray(keywords.toArray());
615624
} else {
616625
throw CompilerDirectives.shouldNotReachHere();
617626
}

src/main/java/org/truffleruby/core/hash/library/HashStoreLibrary.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,14 @@ public static HashStoreLibrary createDispatched() {
4747
return FACTORY.createDispatched(HashGuards.hashStrategyLimit());
4848
}
4949

50+
public static HashStoreLibrary getUncached(RubyHash hash) {
51+
final Object store = hash.store;
52+
return FACTORY.getUncached(store);
53+
}
54+
5055
public static boolean verify(RubyHash hash) {
5156
final Object store = hash.store;
52-
return FACTORY.getUncached(store).verify(store, hash);
57+
return getUncached(hash).verify(store, hash);
5358
}
5459

5560
/** Looks up the key in the hash and returns the associated value, or the result of calling {@code defaultNode} if

src/main/java/org/truffleruby/language/arguments/KeywordArgumentsDescriptor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@
99
*/
1010
package org.truffleruby.language.arguments;
1111

12+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
13+
1214
/** An arguments descriptor that says that keyword arguments were passed (either foo(a: 1) or foo(**kw)). Note that
1315
* currently, if kw is empty, then this descriptor is used even though it is semantically the same as if no keyword
1416
* arguments were passed. The callee must handle that for now. */
1517
public class KeywordArgumentsDescriptor extends ArgumentsDescriptor {
1618

17-
public static final KeywordArgumentsDescriptor INSTANCE = new KeywordArgumentsDescriptor();
19+
@CompilationFinal(dimensions = 1) private final String[] keywords;
1820

19-
private KeywordArgumentsDescriptor() {
21+
/** Use {@link KeywordArgumentsDescriptorManager} to get an instance. */
22+
KeywordArgumentsDescriptor(String[] keywords) {
23+
this.keywords = keywords;
2024
}
2125

26+
public String[] getKeywords() {
27+
return keywords;
28+
}
2229
}

0 commit comments

Comments
 (0)