From ca1603f772f9a0dcdb4a2938a319778072edc0f1 Mon Sep 17 00:00:00 2001 From: Todd White Date: Wed, 12 Aug 2026 21:16:52 -0400 Subject: [PATCH] Skip the C++ construct walk with a class flag call_cxx_construct_for_class recurses up the superclass chain on every allocation. A class whose superclasses have no .cxx_construct between them walks the whole chain to do nothing, and the check is a no-op for nearly every class in a Foundation workload. A class flag means this class and all of its superclasses have no .cxx_construct, so the walk ends at the first class that has one. It is set where the dispatch table is built, by which point the superclass chain is resolved, and cleared where a method is installed. The selector is registered in the init phase rather than tested for null on every recursive call. Measured on whole workloads rather than a loop around the call. Against master, minimum of 5 interleaved rounds: Foundation string, array and dictionary churn under ARC 2.3% faster, and allocation of classes compiled with ARC 19.7% faster, the latter because ARC emits no .cxx_construct at all so the walk ends immediately. A genomic coder that is AVX2 bound with few allocations is unchanged. The matching flag for .cxx_destruct is not here. It measured between 0.3 and 0.6 points on the same workloads, which is at or below the run to run spread of the machine, and it costs a conditional branch in the destruction path. Suite passes, 198 of 198. --- class.h | 7 +++++++ dtable.c | 23 +++++++++++++++-------- loader.c | 4 ++++ loader.h | 5 +++++ runtime.c | 35 ++++++++++++++++++++--------------- selector.h | 8 ++++++++ 6 files changed, 59 insertions(+), 23 deletions(-) diff --git a/class.h b/class.h index 090d7132..454a08c6 100644 --- a/class.h +++ b/class.h @@ -348,6 +348,13 @@ enum objc_class_flags * the underlying blocks runtime. */ objc_class_flag_is_block = (1 << 16), + /** + * Neither this class nor any of its superclasses has a `.cxx_construct` + * method, so constructing an instance of it has nothing to do. Set when + * the class's dispatch table is created, cleared if a `.cxx_construct` is + * installed later. + */ + objc_class_flag_no_cxx_construct = (1<<17), }; /** diff --git a/dtable.c b/dtable.c index 54c0d729..5108c8e3 100644 --- a/dtable.c +++ b/dtable.c @@ -373,17 +373,12 @@ static BOOL installMethodInDtable(Class class, SparseArrayInsert(dtable, untyped_idx, method); #endif - static SEL cxx_construct, cxx_destruct; - if (NULL == cxx_construct) - { - cxx_construct = sel_registerName(".cxx_construct"); - cxx_destruct = sel_registerName(".cxx_destruct"); - } - if (selEqualUnTyped(method->selector, cxx_construct)) + if (selEqualUnTyped(method->selector, cxx_construct_sel)) { class->cxx_construct = method->imp; + objc_clear_class_flag(class, objc_class_flag_no_cxx_construct); } - else if (selEqualUnTyped(method->selector, cxx_destruct)) + else if (selEqualUnTyped(method->selector, cxx_destruct_sel)) { class->cxx_destruct = method->imp; } @@ -604,6 +599,18 @@ PRIVATE dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) list = list->next; } + // The superclass chain is fully initialised by this point, so a class + // whose superclass has nothing to construct and which has installed no + // `.cxx_construct` of its own has nothing to construct either. + if ((NULL == super) || + objc_test_class_flag(super, objc_class_flag_no_cxx_construct)) + { + if (NULL == class->cxx_construct) + { + objc_set_class_flag(class, objc_class_flag_no_cxx_construct); + } + } + return dtable; } diff --git a/loader.c b/loader.c index f4df0471..d44b381b 100644 --- a/loader.c +++ b/loader.c @@ -62,6 +62,10 @@ static void init_runtime(void) #if defined(EMBEDDED_BLOCKS_RUNTIME) init_trampolines(); #endif + // Registering a selector resizes the dispatch tables, which walks the + // class table, so this follows the tables it needs and precedes the + // first class whose methods are installed. + init_cxx_selectors(); init_builtin_classes(); first_run = NO; if (getenv("LIBOBJC_MEMORY_PROFILE")) diff --git a/loader.h b/loader.h index 541c31ee..c0446d8b 100644 --- a/loader.h +++ b/loader.h @@ -100,6 +100,11 @@ void init_protocol_table(void); */ void init_selector_tables(void); +/** + * Register the `.cxx_construct` and `.cxx_destruct` selectors. + */ +void init_cxx_selectors(void); + /** * Initialise the trampolines for using blocks as methods. */ diff --git a/runtime.c b/runtime.c index 45b290e9..14137b10 100644 --- a/runtime.c +++ b/runtime.c @@ -25,16 +25,20 @@ static inline void safe_remove_from_subclass_list(Class cls); PRIVATE BOOL objc_resolve_class(Class); void objc_send_initialize(id object); +PRIVATE SEL cxx_construct_sel; +PRIVATE SEL cxx_destruct_sel; + +PRIVATE void init_cxx_selectors(void) +{ + cxx_construct_sel = sel_registerName(".cxx_construct"); + cxx_destruct_sel = sel_registerName(".cxx_destruct"); +} + /** * Calls C++ destructors in the correct order. */ PRIVATE void call_cxx_destruct(id obj) { - static SEL cxx_destruct; - if (NULL == cxx_destruct) - { - cxx_destruct = sel_registerName(".cxx_destruct"); - } // Don't call object_getClass(), because we want to get hidden classes too Class cls = classForObject(obj); @@ -46,32 +50,33 @@ PRIVATE void call_cxx_destruct(id obj) cls = cls->super_class; if (currentClass->cxx_destruct) { - currentClass->cxx_destruct(obj, cxx_destruct); + currentClass->cxx_destruct(obj, cxx_destruct_sel); } } } static void call_cxx_construct_for_class(Class cls, id obj) { - static SEL cxx_construct; - if (NULL == cxx_construct) - { - cxx_construct = sel_registerName(".cxx_construct"); - } - - if (cls->super_class) + if (cls->super_class && + !objc_test_class_flag(cls->super_class, objc_class_flag_no_cxx_construct)) { call_cxx_construct_for_class(cls->super_class, obj); } if (cls->cxx_construct) { - cls->cxx_construct(obj, cxx_construct); + cls->cxx_construct(obj, cxx_construct_sel); } } PRIVATE void call_cxx_construct(id obj) { - call_cxx_construct_for_class(classForObject(obj), obj); + Class cls = classForObject(obj); + + if (objc_test_class_flag(cls, objc_class_flag_no_cxx_construct)) + { + return; + } + call_cxx_construct_for_class(cls, obj); } /** diff --git a/selector.h b/selector.h index c9409873..6b741b4a 100644 --- a/selector.h +++ b/selector.h @@ -2,6 +2,7 @@ #define OBJC_SELECTOR_H_INCLUDED #include #include "objc/runtime.h" +#include "visibility.h" /** * Structure used to store selectors in the list. @@ -56,6 +57,13 @@ extern "C" */ SEL objc_register_selector(SEL aSel); +/** + * The `.cxx_construct` and `.cxx_destruct` selectors, registered once during + * runtime initialisation by `init_cxx_selectors`. + */ +PRIVATE extern SEL cxx_construct_sel; +PRIVATE extern SEL cxx_destruct_sel; + #ifdef __cplusplus } #endif