diff --git a/pyre/bench/synth/wrapper_subclass_get_override.cranelift.jitstats b/pyre/bench/synth/wrapper_subclass_get_override.cranelift.jitstats new file mode 100644 index 00000000000..68b5ccdf447 --- /dev/null +++ b/pyre/bench/synth/wrapper_subclass_get_override.cranelift.jitstats @@ -0,0 +1,15 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=6 +internal_compile_panics=0 +loops_aborted=0 +loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/wrapper_subclass_get_override.dynasm.jitstats b/pyre/bench/synth/wrapper_subclass_get_override.dynasm.jitstats new file mode 100644 index 00000000000..68b5ccdf447 --- /dev/null +++ b/pyre/bench/synth/wrapper_subclass_get_override.dynasm.jitstats @@ -0,0 +1,15 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=6 +internal_compile_panics=0 +loops_aborted=0 +loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/wrapper_subclass_get_override.py b/pyre/bench/synth/wrapper_subclass_get_override.py new file mode 100644 index 00000000000..2bf815a1183 --- /dev/null +++ b/pyre/bench/synth/wrapper_subclass_get_override.py @@ -0,0 +1,115 @@ +# `typeobject.py descr_getattribute` resolves an attribute through +# `type(descr).__get__`, so a `classmethod` / `staticmethod` SUBCLASS that +# overrides `__get__` binds through that override. A fold that unwraps +# `w_function` in its place calls the wrapped callable directly and never +# reaches the override, which is why every wrapper type test is exact. +# +# One loop per fold that unwraps a wrapper — class receiver, instance +# receiver, and the `__getattr__` hook's two arms — each reading through a +# direct call so the fold is the shape that records, not a lambda's +# megamorphic call site. Every loop must report the OVERRIDE. +# Sized for the fold, not for throughput: the declining path runs the whole +# descriptor protocol per iteration, so this costs far more per `N` than a +# folded loop. Every loop still compiles at N=2000, leaving a wide margin. +N = 100000 + + +class GetOverridingClassMethod(classmethod): + def __get__(self, obj, cls=None): + return overridden + + +class GetOverridingStaticMethod(staticmethod): + def __get__(self, obj, cls=None): + return overridden + + +def overridden(*args): + return 'override' + + +def wrapped_class(cls, x): + return 'wrapped' + + +def wrapped_static(x): + return 'wrapped' + + +class Attrs: + cm = GetOverridingClassMethod(wrapped_class) + sm = GetOverridingStaticMethod(wrapped_static) + + +class ClassHook: + __getattr__ = GetOverridingClassMethod(wrapped_class) + + +class StaticHook: + __getattr__ = GetOverridingStaticMethod(wrapped_static) + + +def classmethod_on_type(): + seen = None + i = 0 + while i < N: + seen = Attrs.cm(i) + i += 1 + print('classmethod on type', seen) + + +def staticmethod_on_type(): + seen = None + i = 0 + while i < N: + seen = Attrs.sm(i) + i += 1 + print('staticmethod on type', seen) + + +def classmethod_on_instance(): + obj = Attrs() + seen = None + i = 0 + while i < N: + seen = obj.cm(i) + i += 1 + print('classmethod on instance', seen) + + +def staticmethod_on_instance(): + obj = Attrs() + seen = None + i = 0 + while i < N: + seen = obj.sm(i) + i += 1 + print('staticmethod on instance', seen) + + +def classmethod_getattr_hook(): + obj = ClassHook() + seen = None + i = 0 + while i < N: + seen = obj.missing + i += 1 + print('classmethod getattr hook', seen) + + +def staticmethod_getattr_hook(): + obj = StaticHook() + seen = None + i = 0 + while i < N: + seen = obj.missing + i += 1 + print('staticmethod getattr hook', seen) + + +classmethod_on_type() +staticmethod_on_type() +classmethod_on_instance() +staticmethod_on_instance() +classmethod_getattr_hook() +staticmethod_getattr_hook() diff --git a/pyre/bench/synth/wrapper_subclass_get_override.wasm.jitstats b/pyre/bench/synth/wrapper_subclass_get_override.wasm.jitstats new file mode 100644 index 00000000000..68b5ccdf447 --- /dev/null +++ b/pyre/bench/synth/wrapper_subclass_get_override.wasm.jitstats @@ -0,0 +1,15 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=6 +internal_compile_panics=0 +loops_aborted=0 +loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/pyre-interpreter/src/baseobjspace.rs b/pyre/pyre-interpreter/src/baseobjspace.rs index f0ca2ff6c6e..0e30f67f5dd 100644 --- a/pyre/pyre-interpreter/src/baseobjspace.rs +++ b/pyre/pyre-interpreter/src/baseobjspace.rs @@ -9890,7 +9890,7 @@ pub unsafe fn load_method_fast_path( /// follow `type.__getattribute__`). A name the metatype defines as a DATA /// DESCRIPTOR is declined too, since that is the one entry `descr_getattribute` /// selects ahead of the class's own MRO. An uncacheable type and any -/// non-`classmethod` descriptor also decline. +/// descriptor that is not an EXACT `classmethod` also decline. /// /// # Safety /// `w_obj` must be a valid object pointer (null tolerated). @@ -9925,7 +9925,10 @@ pub unsafe fn classmethod_on_type_fast_path( return None; } let w_descr = lookup_in_type(w_type, name)?; - if !pyre_object::is_classmethod(w_descr) { + // EXACT, for the reason the `__getattr__`-hook arm records: a `classmethod` + // subclass overriding `__get__` binds through that override, so unwrapping + // `w_function` in its place calls the wrong callable. + if !unsafe { pyre_object::function::is_exact_classmethod(w_descr) } { return None; } let w_func = pyre_object::function::w_classmethod_get_func(w_descr);