Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
# SOFTWARE.

import sys
import types
import unittest

if sys.implementation.name == "graalpy" and not __graalpython__.is_forced_uncached_interpreter:
Expand All @@ -46,7 +47,7 @@

def assert_contains_bytecode(fun, bytecode_str):
bytecode = __graalpython__.dis(fun)
assert bytecode_str in __graalpython__.dis(fun), bytecode
assert bytecode_str in bytecode, bytecode


def test_read_name_quickening_local():
Expand Down Expand Up @@ -140,6 +141,56 @@ def tester(d):
assert_contains_bytecode(tester, "GetMethod$FastPath")


def test_get_method_module_quickening():
module = types.ModuleType("test_module")
module.func = lambda: "instance"

def tester(mod):
return mod.func()

for _ in range(5):
assert tester(module) == "instance"
assert_contains_bytecode(tester, "GetMethod$ModuleFastPath")

del module.func
module.__getattr__ = lambda name: lambda: "fallback: " + name
assert tester(module) == "fallback: func"


def test_get_method_module_subclass_not_quickened():
class ModuleSubclass(types.ModuleType):
def __getattribute__(self, name):
if name == "func":
return lambda: "override"
return super().__getattribute__(name)

module = ModuleSubclass("test_module_subclass")
module.func = lambda: "instance"

def tester(mod):
return mod.func()

for _ in range(5):
assert tester(module) == "override"


def test_get_method_instance_not_quickened():
class K:
func = 42

obj = K()
obj.func = lambda: "instance"

def tester(o):
return o.func()

for _ in range(5):
assert tester(obj) == "instance"

del obj.func
assert obj.func == 42


@skipUnlessSingleContext
def test_call_nilary_method_quickening_python_function():
def callee():
Expand Down Expand Up @@ -304,7 +355,7 @@ def tester(o):


@skipUnlessSingleContext
def test_get_method_builtin_and_pyclass_quickening():
def test_get_method_builtin_and_pyclass():
class MyDict:
def popitem(self):
return 42
Expand All @@ -317,8 +368,6 @@ def tester(d):
assert tester(d)[0] == i
assert tester(MyDict()) == 42

assert_contains_bytecode(tester, "GetMethod$FastPath")


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@
import com.oracle.graal.python.runtime.sequence.storage.ObjectSequenceStorage;
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
import com.oracle.graal.python.util.ArrayBuilder;
import com.oracle.graal.python.util.InlineWeakValueProfile;
import com.oracle.graal.python.util.PythonUtils;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
Expand Down Expand Up @@ -1934,7 +1933,7 @@ public static boolean doIterator(VirtualFrame frame, LocalAccessor output, Objec
}
}

@Operation(storeBytecodeIndex = true)
@Operation(storeBytecodeIndex = false)
@ConstantOperand(type = TruffleString.class)
@ImportStatic({PythonUtils.class, PGuards.class, GetAttribute.class})
public static final class GetMethod {
Expand All @@ -1955,61 +1954,98 @@ public static Object doStringFastPath(VirtualFrame frame, TruffleString name, Tr
return result;
}

private static boolean hasObjectOrModuleGetattro(Node inliningTarget, PythonManagedClass klass, InlineWeakValueProfile slotsValueProfile) {
TpSlots slots = slotsValueProfile.execute(inliningTarget, klass.getTpSlots());
return GetAttribute.hasObjectOrModuleGetattro(slots);
@ForceQuickening
@Specialization(guards = {
/* static checks: */ "!hasMaterializedDict(cachedShape)", "!isBuiltin(cachedShape)", "noInstanceAttribute", "!isNoValue(result)", //
/* dynamic checks: */ "cachedShape.check(obj)"}, //
assumptions = "typeStableAssumption", //
limit = "2", excludeForUncached = true)
public static Object doFastPath(VirtualFrame frame, TruffleString name, PythonObject obj,
@Cached("obj.getShape()") Shape cachedShape,
@Cached("cachedShape.getProperty(name) == null") boolean noInstanceAttribute,
@Cached(value = "loadCacheableAttr(obj, cachedShape, name)", weak = true) Object result,
@Cached("getTypeStableAssumption(cachedShape)") Assumption typeStableAssumption) {
assert typeStableAssumption != null;
assert PythonLanguage.get(null).isSingleContext(); // implied by type stable assumption being single ctx only
assert obj.checkDictFlags();
return result;
}

@Idempotent
public static boolean isBuiltinWithObjectOrModuleGetattro(Shape cachedShape) {
return cachedShape.getDynamicType() instanceof PythonBuiltinClassType type && GetAttribute.hasObjectOrModuleGetattro(type.getSlots());
public static Assumption getTypeStableAssumption(Shape cachedShape) {
Object type = cachedShape.getDynamicType();
if (type instanceof PythonBuiltinClassType || type instanceof PythonBuiltinClass) {
return null; // meaning: nothing to check
} else if (type instanceof PythonClass pythonClass) {
return pythonClass.getTypeStableAssumption();
}
// earlier guards should have ensured this
throw CompilerDirectives.shouldNotReachHere();
}

public static PythonManagedClass getManagedClassOrNull(Shape cachedShape) {
return cachedShape.getDynamicType() instanceof PythonManagedClass managedClass ? managedClass : null;
@Idempotent
public static boolean isBuiltin(Shape cachedShape) {
return cachedShape.getDynamicType() instanceof PythonBuiltinClassType ||
cachedShape.getDynamicType() instanceof PythonBuiltinClass;
}

@ForceQuickening
@Specialization(guards = {
"!hasMaterializedDict(cachedShape)", "managedClass != null || isBuiltinWithObjectOrModuleGetattro(cachedShape)", //
"cachedShape.check(obj)", "result != null"}, limit = "2", excludeForUncached = true)
public static Object doFastPath(VirtualFrame frame,
TruffleString name, PythonObject obj,
@Bind Node inliningTarget,
/* static checks: */ "!hasMaterializedDict(cachedShape)", "isBuiltin(cachedShape)", "noInstanceAttribute", "!isNoValue(result)", //
/* dynamic checks: */ "cachedShape.check(obj)"}, //
limit = "2", excludeForUncached = true)
public static Object doFastPathBuiltin(VirtualFrame frame, TruffleString name, PythonObject obj,
@Cached("obj.getShape()") Shape cachedShape,
@Cached("getManagedClassOrNull(cachedShape)") PythonManagedClass managedClass,
@Cached("getPropertyGetterWithFinalAssumption(cachedShape, name)") PropertyGetter cachedPropertyGetter,
@Cached InlineWeakValueProfile slotsValueProfile,
@Cached InlinedBranchProfile hasInstanceValueBranchProfile,
@Cached LookupAttributeInMRONode.CachedKeyFastPath getMethod,
@Bind("getMethodFastPath(obj, name, inliningTarget, managedClass, cachedShape, cachedPropertyGetter, slotsValueProfile, hasInstanceValueBranchProfile, getMethod)") Object result) {
@Cached("cachedShape.getProperty(name) == null") boolean noInstanceAttribute,
@Cached(value = "loadCacheableAttr(obj, cachedShape, name)", weak = true) Object result) {
assert obj.checkDictFlags();
return result;
}

static Object getMethodFastPath(PythonObject obj, TruffleString name, Node inliningTarget, PythonManagedClass managedClass, Shape cachedShape, PropertyGetter cachedPropertyGetter,
InlineWeakValueProfile slotsValueProfile, InlinedBranchProfile hasInstanceValueBranchProfile, LookupAttributeInMRONode.CachedKeyFastPath getMethod) {
if (managedClass != null) {
if (!hasObjectOrModuleGetattro(inliningTarget, managedClass, slotsValueProfile)) {
return null;
}
@ForceQuickening
@Specialization(guards = {
/* static checks: */ "!canBeSpecial", "!hasMaterializedDict(cachedShape)", "isBuiltinModule(cachedShape)", "getter != null", //
/* dynamic checks: */ "getter.accepts(obj)"}, //
rewriteOn = GetAttribute.FastPathBailoutException.class, //
limit = "2", excludeForUncached = true)
public static Object doModuleFastPath(VirtualFrame frame, TruffleString name, PythonModule obj,
@Cached("obj.getShape()") Shape cachedShape,
@Cached("canBeSpecialMethod(name)") boolean canBeSpecial,
@Cached("getPropertyGetterWithFinalAssumption(cachedShape, name)") PropertyGetter getter) throws GetAttribute.FastPathBailoutException {
return new BoundDescriptor(GetAttribute.getValue(getter, obj));
}

public static Object loadCacheableAttr(PythonObject object, Shape cachedShape, TruffleString key) {
assert object.checkDictFlags();
Object klass = cachedShape.getDynamicType();
TpSlots klassSlots = null;
if (klass instanceof PythonBuiltinClassType type) {
klassSlots = type.getSlots();
} else if (klass instanceof PythonClass pyClass) {
klassSlots = pyClass.getTpSlots();
}
Object descr = getMethod.execute(inliningTarget, cachedShape.getDynamicType(), name);
if (descr == null || (descr != PNone.NO_VALUE && !MaybeBindDescriptorNode.isMethodDescriptor(descr))) {
return null;
if (klassSlots == null || !GetAttribute.hasObjectOrModuleGetattro(klassSlots)) {
return PNone.NO_VALUE;
}
if (cachedPropertyGetter != null) {
assert obj.checkDictFlags();
Object instanceValue = cachedPropertyGetter.get(obj);
if (instanceValue != PNone.NO_VALUE) {
hasInstanceValueBranchProfile.enter(inliningTarget);
return new BoundDescriptor(instanceValue);
}

// guard should have already checked that the shape doesn't have that key
assert DynamicObject.GetNode.getUncached().execute(object, key, null) == null;

Object descr;
if (klass instanceof PythonBuiltinClassType type) {
descr = LookupAttributeInMRONode.findAttr(type, key);
} else {
descr = LookupAttributeInMRONode.lookupSlowPathNoSideEffects(klass, key);
}

if (MaybeBindDescriptorNode.isMethodDescriptor(descr)) {
return descr;
}
return descr != PNone.NO_VALUE ? descr : null;
return PNone.NO_VALUE;
}

@Specialization(replaces = {"doStringFastPath", "doFastPath"})
@Specialization(replaces = {"doStringFastPath", "doFastPath", "doFastPathBuiltin", "doModuleFastPath"})
@ForceQuickening
@StoreBytecodeIndex
public static Object doIt(VirtualFrame frame,
TruffleString name, Object obj,
@Bind Node inliningTarget,
Expand Down
Loading