Skip to content

Commit a95b526

Browse files
committed
Re-think unary call site test structure
For clarity, we separate our tests of correctness and re-linking. We make the tests work with a mixture types and parameterise the operation, and that way end up with fewer test methods.
1 parent f5368a4 commit a95b526

File tree

6 files changed

+276
-156
lines changed

6 files changed

+276
-156
lines changed

rt4core/src/main/java/uk/co/farowl/vsj4/core/PyFloat.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ public static double asDouble(Object o)
140140
throw Abstract.requiredTypeError("a real number", o);
141141
}
142142

143+
@Override
144+
public String toString() { return PyUtil.defaultToString(this); }
145+
143146
// Constructor from Python ----------------------------------------
144147

145148
/**

rt4core/src/main/java/uk/co/farowl/vsj4/core/PyRT.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,28 @@
2222
import uk.co.farowl.vsj4.support.InterpreterError;
2323

2424
/**
25-
* Run-time support for JVM-compiled code (including
26-
* {@code invokedynamic} call sites). In some ways, this is the
27-
* companion to {@link Abstract}, which supports the interpretation of
28-
* Python byte code, providing access to special methods on Python
29-
* objects, through a regular Java API called with self-type
30-
* {@code Object}. Here, in contrast, we provide access to the same
31-
* special methods through the {@code invokedynamic} mechanism, for the
32-
* benefit of Java byte code call sites emitted by the Python compiler.
25+
* {@link PyRT} provides run-time support for Python that has been
26+
* compiled to Java byte code, primarily for {@code invokedynamic} call
27+
* sites. In some ways, this supersedes methods in {@link Abstract} that
28+
* support the interpretation of Python byte code. Like those methods,
29+
* these call sites often wrap a call on a particular special method.
30+
* Call sites in Java code should behave exactly as their counterparts
31+
* in {@link Abstract}.
3332
* <p>
3433
* The use of {@code invokedynamic} call sites in compiled code offers a
3534
* greater potential for dynamic optimisation through specialisation to
3635
* the actual Java classes encountered in a given place. (It does not
37-
* benefit code like that in {@link Abstract} which receives many
38-
* different types.)
36+
* benefit widely used code that receives calls with many different
37+
* object types.)
3938
* <p>
4039
* The fact that this specialisation is on Java class rather than Python
4140
* type has two implications.
4241
* <ol>
4342
* <li>Primitive operations on simple types (like {@code int.__neg__})
44-
* dispatch quickly to their exact target implementation</li>
45-
* <li>Python types that share an implementation class, largely those
46-
* defined in Python, dispatch through a lookup on their particular type
47-
* object.</li>
43+
* dispatch quickly to their exact target implementation.</li>
44+
* <li>Operations on Python types that share an implementation class,
45+
* largely those defined in Python, must find their target in a second
46+
* step via the Python type of {@code self}.</li>
4847
* </ol>
4948
*/
5049
public class PyRT {
@@ -100,6 +99,8 @@ public static CallSite bootstrap(Lookup lookup, String name,
10099
// TODO Maybe use AST node names/enum for call sites?
101100
case "negative" -> new UnaryOpCallSite(
102101
SpecialMethod.op_neg);
102+
case "absolute" -> new UnaryOpCallSite(
103+
SpecialMethod.op_abs);
103104
case "add" -> new BinaryOpCallSite(SpecialMethod.op_add);
104105
case "multiply" -> new BinaryOpCallSite(
105106
SpecialMethod.op_mul);
@@ -112,8 +113,6 @@ public static CallSite bootstrap(Lookup lookup, String name,
112113
return site;
113114
}
114115

115-
// enum Validity {CLASS, TYPE, INSTANCE, ONCE}
116-
117116
/**
118117
* A call site for unary Python operations. The call site is
119118
* constructed from a slot such as {@link SpecialMethod#op_neg}. It
@@ -123,6 +122,9 @@ public static CallSite bootstrap(Lookup lookup, String name,
123122
*/
124123
static class UnaryOpCallSite extends MutableCallSite {
125124

125+
/** Limit on {@link #chainLength}. */
126+
public static final int MAX_CHAIN = 4;
127+
126128
/**
127129
* Handle to {@link #fallback(Object)}, which is the behaviour
128130
* for this call site when the class of {@code self} does not
@@ -138,8 +140,8 @@ static class UnaryOpCallSite extends MutableCallSite {
138140
}
139141
}
140142

141-
/** The abstract operation to be applied by the site. */
142-
private final SpecialMethod op;
143+
/** The {@link SpecialMethod} to be applied by the site. */
144+
final SpecialMethod op;
143145

144146
/**
145147
* The number of times this site has used
@@ -148,6 +150,14 @@ static class UnaryOpCallSite extends MutableCallSite {
148150
*/
149151
int fallbackCount;
150152

153+
/**
154+
* The number of guarded invocations cached in the target of
155+
* this site by {@link #fallback(Object) fallback}, used to
156+
* observe internal working and potentially for de-optimisation
157+
* decisions.
158+
*/
159+
int chainLength;
160+
151161
/**
152162
* Construct a call site with the specific unary operation.
153163
*
@@ -198,12 +208,13 @@ private Object fallback(Object self) throws Throwable {
198208
* If the type has chosen a generic handle, it is because
199209
* the meaning of the special method may change.
200210
*/
201-
if (mh != op.generic) {
211+
if (mh != op.generic && chainLength < MAX_CHAIN) {
202212
// MH for guarded invocation (becomes new target)
203213
MethodHandle guardMH = CLASS_GUARD.bindTo(selfClass);
204214
MethodHandle targetMH =
205215
guardWithTest(guardMH, mh, getTarget());
206216
setTarget(targetMH);
217+
chainLength += 1;
207218
}
208219
return result;
209220
}

rt4core/src/main/java/uk/co/farowl/vsj4/kernel/BaseType.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c)2025 Jython Developers.
1+
// Copyright (c)2026 Jython Developers.
22
// Licensed to PSF under a contributor agreement.
33
package uk.co.farowl.vsj4.kernel;
44

@@ -1242,7 +1242,7 @@ void fillConstructorLookup(TypeSpec spec) {
12421242
private void updateSpecialMethodCache(SpecialMethod sm,
12431243
LookupResult result) {
12441244

1245-
if (sm.cache == null) {
1245+
if (sm.hasCache()) {
12461246
// There is no cache for this special method. Ignore.
12471247
return;
12481248

rt4core/src/main/java/uk/co/farowl/vsj4/kernel/SpecialMethod.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -795,16 +795,16 @@ Object slot(Object self) throws Throwable {
795795
Object meth = type.lookup(methodName);
796796
if (meth == null) { throw SMUtil.EMPTY; }
797797
// What kind of object did we find? (Could be anything.)
798-
Representation rep = Representation.get(meth);
799-
PyType methType = rep.pythonType(meth);
798+
Representation methRep = Representation.get(meth);
799+
PyType methType = methRep.pythonType(meth);
800800

801801
if (methType.isMethodDescr()) {
802802
return Callables.call(meth, self);
803803
} else {
804804
// We might still have have to bind meth to self.
805805
if (methType.isDescr()) {
806806
// Replace meth with result of descriptor binding.
807-
meth = op_get.handle(rep).invokeExact(meth, self, type);
807+
meth = methRep.op_get().invokeExact(meth, self, type);
808808
}
809809
// meth is now the thing to call.
810810
return Callables.call(meth);
@@ -833,16 +833,16 @@ Object slot(Object self, Object w) throws Throwable {
833833
Object meth = type.lookup(methodName);
834834
if (meth == null) { throw SMUtil.EMPTY; }
835835
// What kind of object did we find? (Could be anything.)
836-
Representation rep = Representation.get(meth);
837-
PyType methType = rep.pythonType(meth);
836+
Representation methRep = Representation.get(meth);
837+
PyType methType = methRep.pythonType(meth);
838838

839839
if (methType.isMethodDescr()) {
840840
return Callables.call(meth, self, w);
841841
} else {
842842
// We might still have have to bind meth to self.
843843
if (methType.isDescr()) {
844844
// Replace meth with result of descriptor binding.
845-
meth = op_get.handle(rep).invokeExact(meth, self, type);
845+
meth = methRep.op_get().invokeExact(meth, self, type);
846846
}
847847
// meth is now the thing to call.
848848
return Callables.call(meth, w);
@@ -872,16 +872,16 @@ Object slot(Object self, Object w, Object m) throws Throwable {
872872
Object meth = type.lookup(methodName);
873873
if (meth == null) { throw SMUtil.EMPTY; }
874874
// What kind of object did we find? (Could be anything.)
875-
Representation rep = Representation.get(meth);
876-
PyType methType = rep.pythonType(meth);
875+
Representation methRep = Representation.get(meth);
876+
PyType methType = methRep.pythonType(meth);
877877

878878
if (methType.isMethodDescr()) {
879879
return Callables.call(meth, self, w, m);
880880
} else {
881881
// We might still have have to bind meth to self.
882882
if (methType.isDescr()) {
883883
// Replace meth with result of descriptor binding.
884-
meth = op_get.handle(rep).invokeExact(meth, self, type);
884+
meth = methRep.op_get().invokeExact(meth, self, type);
885885
}
886886
// meth is now the thing to call.
887887
return Callables.call(meth, w, m);
@@ -913,16 +913,16 @@ Object slot(Object self, Object obj, PyType t) throws Throwable {
913913
Object meth = type.lookup(methodName);
914914
if (meth == null) { throw SMUtil.EMPTY; }
915915
// What kind of object did we find? (Could be anything.)
916-
Representation rep = Representation.get(meth);
917-
PyType methType = rep.pythonType(meth);
916+
Representation methRep = Representation.get(meth);
917+
PyType methType = methRep.pythonType(meth);
918918

919919
if (methType.isMethodDescr()) {
920920
return Callables.call(meth, self, obj, t);
921921
} else {
922922
// We might still have have to bind meth to self.
923923
if (methType.isDescr()) {
924924
// Replace meth with result of descriptor binding.
925-
meth = op_get.handle(rep).invokeExact(meth, self, type);
925+
meth = methRep.op_get().invokeExact(meth, self, type);
926926
}
927927
// meth is now the thing to call.
928928
return Callables.call(meth, obj, t);
@@ -947,6 +947,16 @@ public MethodHandle errorHandle() {
947947
return error;
948948
}
949949

950+
/**
951+
* Whether this {@code SpecialMethod} has a corresponding cache in
952+
* {@link Representation} objects. If not, the effective
953+
* {@code MethodHandle} is always the {@link #generic} one.
954+
*
955+
* @param rep target {@code Representation}
956+
* @param mh handle value to assign
957+
*/
958+
public boolean hasCache() { return cache != null; }
959+
950960
/**
951961
* Set the cache for this {@code SpecialMethod} in the
952962
* {@link Representation} to the given {@code MethodHandle}.

0 commit comments

Comments
 (0)