feat(blocks runtime): Enable using libBlocksRuntime instead of the embedded runtime

main
Niels Grewe 2 years ago committed by David Chisnall
parent dc031d2741
commit 2855d17714

@ -1,7 +1,7 @@
libcxxrt_freebsd_task:
matrix:
- freebsd_instance:
image_family: freebsd-13-2
image_family: freebsd-13-3
- freebsd_instance:
image_family: freebsd-15-0-snap
- freebsd_instance:

@ -20,6 +20,7 @@ jobs:
# Build each combination of OS and release/debug variants
os: [ "ubuntu-22.04", "ubuntu-20.04" ]
build-type: [ Release, Debug ]
blocks-runtime: [ "EMBEDDED", "swift-5.10-RELEASE" ]
cxxlib: [ "libc++", "libstdc++" ]
llvm-version: [10, 11, 12, 13, 14, 15]
# Don't bother testing the LLVM versions that aren't in the default image for the different platforms
@ -41,7 +42,7 @@ jobs:
# Don't abort runners if a single one fails
fail-fast: false
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} ${{ matrix.build-type }} LLVM-${{ matrix.llvm-version }} ${{ matrix.cxxlib }}
name: ${{ matrix.os }} ${{ matrix.build-type }} LLVM-${{ matrix.llvm-version }} ${{ matrix.cxxlib }} BlocksRuntime-${{ matrix.blocks-runtime }}
steps:
- uses: actions/checkout@v3
- name: Install dependencies
@ -53,11 +54,24 @@ jobs:
sudo apt install libc++-${{matrix.llvm-version}}-dev libc++abi-${{matrix.llvm-version}}-dev
sudo apt install libunwind-${{matrix.llvm-version}}-dev || true
fi
if [ "${{ matrix.blocks-runtime }}" != "EMBEDDED" ]; then
git clone --depth 1 --branch "${{ matrix.blocks-runtime }}" https://github.com/apple/swift-corelibs-libdispatch.git ${{github.workspace}}/swift-corelibs-libdispatch
cmake -B ${{github.workspace}}/swift-corelibs-libdispatch/build -G Ninja -DINSTALL_PRIVATE_HEADERS=ON -DCMAKE_C_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm-version}} -S ${{github.workspace}}/swift-corelibs-libdispatch
pushd ${{github.workspace}}/swift-corelibs-libdispatch/build
ninja
sudo ninja install
popd
fi
- name: Configure CMake
run: |
export LDFLAGS=-L/usr/lib/llvm-${{ matrix.llvm-version }}/lib/
if [ "${{ matrix.blocks-runtime }}" != "EMBEDDED" ]; then
export EMBEDDED_BLOCKS_RUNTIME=OFF
else
export EMBEDDED_BLOCKS_RUNTIME=ON
fi
ls -lahR /usr/lib/llvm-${{ matrix.llvm-version }}/lib/
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -G Ninja -DTESTS=ON -DCMAKE_C_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_OBJC_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_ASM_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm-version}} -DCMAKE_OBJCXX_COMPILER=clang++-${{matrix.llvm-version}} -DCMAKE_CXX_FLAGS="-stdlib=${{matrix.cxxlib}}"
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -G Ninja -DTESTS=ON -DEMBEDDED_BLOCKS_RUNTIME=$EMBEDDED_BLOCKS_RUNTIME -DCMAKE_C_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_OBJC_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_ASM_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm-version}} -DCMAKE_OBJCXX_COMPILER=clang++-${{matrix.llvm-version}} -DCMAKE_CXX_FLAGS="-stdlib=${{matrix.cxxlib}}"
# Build with a nice ninja status line
- name: Build
working-directory: ${{github.workspace}}/build

@ -28,6 +28,7 @@ endif ()
INCLUDE (CheckCXXSourceCompiles)
INCLUDE (FetchContent)
INCLUDE (CheckSymbolExists)
set(libobjc_VERSION 4.6)
@ -58,7 +59,7 @@ set(libobjc_OBJC_SRCS
NSBlocks.m
Protocol2.m
associate.m
blocks_runtime.m
blocks_runtime_np.m
properties.m)
set(libobjc_C_SRCS
alias_table.c
@ -83,8 +84,6 @@ set(libobjc_HDRS
objc/Availability.h
objc/Object.h
objc/Protocol.h
objc/blocks_private.h
objc/blocks_runtime.h
objc/capabilities.h
objc/developer.h
objc/encoding.h
@ -101,10 +100,7 @@ set(libobjc_HDRS
objc/runtime-deprecated.h
objc/runtime.h
objc/slot.h)
set(libBlocksRuntime_COMPATIBILITY_HDRS
Block.h
Block_private.h
)
set(libobjc_CXX_SRCS
selector_table.cc
)
@ -143,7 +139,7 @@ option(DEBUG_ARC_COMPAT
"Log warnings for classes that don't hit ARC fast paths" OFF)
option(ENABLE_OBJCXX "Enable support for Objective-C++" ON)
option(TESTS "Enable building the tests")
option(EMBEDDED_BLOCKS_RUNTIME "Include an embedded blocks runtime, rather than relying on libBlocksRuntime to supply it" ON)
# For release builds, we disable spamming the terminal with warnings about
# selector type mismatches
@ -230,6 +226,31 @@ else ()
find_library(M_LIBRARY m)
endif ()
if (EMBEDDED_BLOCKS_RUNTIME)
set(libBlocksRuntime_COMPATIBILITY_HDRS
Block.h
Block_private.h
)
list(APPEND libobjc_OBJC_SRCS blocks_runtime.m)
list(APPEND libobjc_HDRS objc/blocks_private.h)
list(APPEND libobjc_HDRS objc/blocks_runtime.h)
add_definitions(-DEMBEDDED_BLOCKS_RUNTIME)
else ()
find_library(BLOCKS_RUNTIME_LIBRARY BlocksRuntime)
if (BLOCKS_RUNTIME_LIBRARY)
set(CMAKE_REQUIRED_LIBRARIES ${BLOCKS_RUNTIME_LIBRARY})
check_symbol_exists(_Block_use_RR2 "Block_private.h" HAVE_BLOCK_USE_RR2)
if (HAVE_BLOCK_USE_RR2)
add_definitions(-DHAVE_BLOCK_USE_RR2)
else ()
message(FATAL_ERROR "libBlocksRuntime does not contain _Block_use_RR2(). Enable EMBEDDED_BLOCKS_RUNTIME to use the built-in blocks runtime.")
endif ()
unset(CMAKE_REQUIRED_LIBRARIES)
else ()
message(FATAL_ERROR "libBlocksRuntime not found. Enable EMBEDDED_BLOCKS_RUNTIME to use the built-in blocks runtime.")
endif ()
endif ()
add_library(objc SHARED ${libobjc_C_SRCS} ${libobjc_ASM_SRCS} ${libobjc_OBJC_SRCS} ${libobjc_OBJCXX_SRCS} ${libobjc_ASM_OBJS})
target_compile_options(objc PRIVATE "$<$<OR:$<COMPILE_LANGUAGE:OBJC>,$<COMPILE_LANGUAGE:OBJCXX>>:-Wno-deprecated-objc-isa-usage;-Wno-objc-root-class;-fobjc-runtime=gnustep-2.0>$<$<COMPILE_LANGUAGE:C>:-Xclang;-fexceptions>")
@ -268,6 +289,10 @@ if (M_LIBRARY)
target_link_libraries(objc PUBLIC ${M_LIBRARY})
endif ()
if (BLOCKS_RUNTIME_LIBRARY)
target_link_libraries(objc PUBLIC ${BLOCKS_RUNTIME_LIBRARY})
endif ()
# Make weak symbols work on OS X
if (APPLE)
set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS
@ -340,8 +365,12 @@ install(TARGETS ${INSTALL_TARGETS}
install(FILES ${libobjc_HDRS}
DESTINATION "${HEADER_INSTALL_PATH}/${INCLUDE_DIRECTORY}")
install(FILES ${libBlocksRuntime_COMPATIBILITY_HDRS}
if (EMBEDDED_BLOCKS_RUNTIME)
install(FILES ${libBlocksRuntime_COMPATIBILITY_HDRS}
DESTINATION "${HEADER_INSTALL_PATH}")
endif ()
set(CPACK_GENERATOR TGZ CACHE STRING
"Installer types to generate. Sensible options include TGZ, RPM and DEB")
@ -386,6 +415,9 @@ set(PC_LIBS_PRIVATE ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES})
if (M_LIBRARY)
list(APPEND PC_LIBS_PRIVATE ${M_LIBRARY})
endif ()
if (BLOCKS_RUNTIME_LIBRARY)
list(APPEND PC_LIBS_PRIVATE ${BLOCKS_RUNTIME_LIBRARY})
endif ()
list(REMOVE_DUPLICATES PC_LIBS_PRIVATE)
string(REPLACE ";" " -l" PC_LIBS_PRIVATE "${PC_LIBS_PRIVATE}")
set(PC_LIBS_PRIVATE "Libs.private: -l${PC_LIBS_PRIVATE}")

@ -79,6 +79,20 @@ this configuration, we provide a separate libobjcxx.so, which avoids the need
for the Objective-C runtime to depend on the STL implementation just to be able
to interoperate with C++ exceptions.
Blocks Runtime Integration
--------------------------
libobjc2 ships with a runtime for the blocks C extension (i.e. closures/lambdas) and
will install compatibility headers for the libBlocksRuntime library that ships with
LLVM's compiler-rt or Swift's libdispatch. Alternatively, libobjc2 can be built without
the embedded blocks runtime and utilise the one from libdispatch instead. This can be
enabled by adding `-DEMBEDDED_BLOCKS_RUNTIME=OFF` to the `cmake` command. It's required
that your version of libBlocksRuntime provides the `Blocks_private.h` header.
(enabled with `-DINSTALL_PRIVATE_HEADERS=ON` when building libdispatch from source)
Regardless of the chosen blocks runtime implementation, blocks will be fully integrated
into the Objective-C runtime.
Installation Location
---------------------

@ -6,13 +6,23 @@
#include "dtable.h"
#include <assert.h>
OBJC_PUBLIC struct objc_class _NSConcreteGlobalBlock;
OBJC_PUBLIC struct objc_class _NSConcreteStackBlock;
OBJC_PUBLIC struct objc_class _NSConcreteMallocBlock;
#ifdef EMBEDDED_BLOCKS_RUNTIME
#define BLOCK_STORAGE OBJC_PUBLIC
#else
#define BLOCK_STORAGE extern
#endif
BLOCK_STORAGE struct objc_class _NSConcreteGlobalBlock;
BLOCK_STORAGE struct objc_class _NSConcreteStackBlock;
BLOCK_STORAGE struct objc_class _NSConcreteMallocBlock;
BLOCK_STORAGE struct objc_class _NSConcreteAutoBlock;
BLOCK_STORAGE struct objc_class _NSConcreteFinalizingBlock;
static struct objc_class _NSConcreteGlobalBlockMeta;
static struct objc_class _NSConcreteStackBlockMeta;
static struct objc_class _NSConcreteMallocBlockMeta;
static struct objc_class _NSConcreteAutoBlockMeta;
static struct objc_class _NSConcreteFinalizingBlockMeta;
static struct objc_class _NSBlock;
static struct objc_class _NSBlockMeta;
@ -31,6 +41,7 @@ static void createNSBlockSubclass(Class superclass, Class newClass,
newClass->super_class = superclass;
newClass->name = name;
newClass->dtable = uninstalled_dtable;
newClass->info = objc_class_flag_is_block;
LOCK_RUNTIME_FOR_SCOPE();
objc_load_class(newClass);
@ -49,8 +60,20 @@ BOOL objc_create_block_classes_as_subclasses_of(Class super)
NEW_CLASS(&_NSBlock, _NSConcreteStackBlock);
NEW_CLASS(&_NSBlock, _NSConcreteGlobalBlock);
NEW_CLASS(&_NSBlock, _NSConcreteMallocBlock);
NEW_CLASS(&_NSBlock, _NSConcreteAutoBlock);
NEW_CLASS(&_NSBlock, _NSConcreteFinalizingBlock);
// Global blocks never need refcount manipulation.
objc_set_class_flag(&_NSConcreteGlobalBlock,
objc_class_flag_permanent_instances);
return YES;
}
PRIVATE void init_early_blocks(void)
{
if (_NSBlock.super_class != NULL) { return; }
_NSConcreteStackBlock.info = objc_class_flag_is_block;
_NSConcreteGlobalBlock.info = objc_class_flag_is_block | objc_class_flag_permanent_instances;
_NSConcreteMallocBlock.info = objc_class_flag_is_block;
_NSConcreteAutoBlock.info = objc_class_flag_is_block;
_NSConcreteFinalizingBlock.info = objc_class_flag_is_block;
}

@ -10,14 +10,19 @@
#include <tsl/robin_map.h>
#import "lock.h"
#import "objc/runtime.h"
#ifdef EMBEDDED_BLOCKS_RUNTIME
#import "objc/blocks_private.h"
#import "objc/blocks_runtime.h"
#else
#include <Block.h>
#include <Block_private.h>
#endif
#import "nsobject.h"
#import "class.h"
#import "selector.h"
#import "visibility.h"
#import "objc/hooks.h"
#import "objc/objc-arc.h"
#import "objc/blocks_runtime.h"
#include "objc/message.h"
/**
@ -75,12 +80,16 @@ static inline arc_tls_key_t arc_tls_key_create(arc_cleanup_function_t cleanupFun
arc_tls_key_t ARCThreadKey;
#endif
#ifndef HAVE_BLOCK_USE_RR2
extern "C"
{
extern struct objc_class _NSConcreteMallocBlock;
extern struct objc_class _NSConcreteStackBlock;
extern struct objc_class _NSConcreteGlobalBlock;
extern struct objc_class _NSConcreteAutoBlock;
extern struct objc_class _NSConcreteFinalizingBlock;
}
#endif
@interface NSAutoreleasePool
+ (Class)class;
@ -315,8 +324,7 @@ static inline id retain(id obj, BOOL isWeak)
{
if (isPersistentObject(obj)) { return obj; }
Class cls = obj->isa;
if ((Class)&_NSConcreteMallocBlock == cls ||
(Class)&_NSConcreteStackBlock == cls)
if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block)))
{
return Block_copy(obj);
}
@ -376,13 +384,13 @@ static inline void release(id obj)
{
if (isPersistentObject(obj)) { return; }
Class cls = obj->isa;
if (cls == static_cast<Class>(&_NSConcreteMallocBlock))
if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block)))
{
if (cls == static_cast<void*>(&_NSConcreteStackBlock))
{
_Block_release(obj);
return;
}
if (cls == static_cast<Class>(&_NSConcreteStackBlock))
{
_Block_release(obj);
return;
}
if (objc_test_class_flag(cls, objc_class_flag_fast_arc))
@ -702,12 +710,24 @@ mutex_t weakRefLock;
}
#ifdef HAVE_BLOCK_USE_RR2
static const struct Block_callbacks_RR blocks_runtime_callbacks = {
sizeof(Block_callbacks_RR),
(void (*)(const void*))objc_retain,
(void (*)(const void*))objc_release,
(void (*)(const void*))objc_delete_weak_refs
};
#endif
PRIVATE extern "C" void init_arc(void)
{
INIT_LOCK(weakRefLock);
#ifdef arc_tls_store
ARCThreadKey = arc_tls_key_create((arc_cleanup_function_t)cleanupPools);
#endif
#ifdef HAVE_BLOCK_USE_RR2
_Block_use_RR2(&blocks_runtime_callbacks);
#endif
}
/**
@ -843,9 +863,18 @@ extern "C" OBJC_PUBLIC id objc_storeWeak(id *addr, id obj)
*addr = obj;
return obj;
}
// If the object is being deallocated return nil.
if (object_getRetainCount_np(obj) == 0)
Class cls = classForObject(obj);
if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block)))
{
// Check whether the block is being deallocated and return nil if so
if (_Block_isDeallocating(obj)) {
*addr = nil;
return nil;
}
}
else if (object_getRetainCount_np(obj) == 0)
{
// If the object is being deallocated return nil.
*addr = nil;
return nil;
}
@ -916,19 +945,31 @@ extern "C" OBJC_PUBLIC id objc_loadWeakRetained(id* addr)
return nil;
}
Class cls = classForObject(obj);
if (static_cast<Class>(&_NSConcreteMallocBlock) == cls)
if (objc_test_class_flag(cls, objc_class_flag_permanent_instances))
{
return obj;
}
else if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block)))
{
obj = static_cast<id>(block_load_weak(obj));
if (obj == nil)
{
return nil;
}
else if (objc_test_class_flag(cls, objc_class_flag_permanent_instances))
// This is a defeasible retain operation that protects against another thread concurrently
// starting to deallocate the block.
if (_Block_tryRetain(obj))
{
return obj;
}
return nil;
}
else if (!objc_test_class_flag(cls, objc_class_flag_fast_arc))
{
obj = _objc_weak_load(obj);
}
// block_load_weak() or _objc_weak_load() can return nil
// _objc_weak_load() can return nil
if (obj == nil) { return nil; }
return retain(obj, YES);
}

@ -24,6 +24,7 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import "objc/blocks_runtime.h"
#include "objc/blocks_private.h"
#import "objc/runtime.h"
#import "objc/objc-arc.h"
#include "blocks_runtime.h"
@ -60,10 +61,6 @@ OBJC_PUBLIC const char * _Block_signature(void *b)
}
return block->descriptor->encoding;
}
OBJC_PUBLIC const char *block_getType_np(const void *b)
{
return _Block_signature((void*)b);
}
static int increment24(int *ref)
{
@ -296,8 +293,32 @@ OBJC_PUBLIC void _Block_release(const void *src)
}
}
PRIVATE void* block_load_weak(void *block)
OBJC_PUBLIC bool _Block_isDeallocating(const void* arg)
{
struct Block_layout *block = (struct Block_layout*)arg;
int *refCountPtr = &((struct Block_layout*)arg)->reserved;
int refCount = __sync_fetch_and_add(refCountPtr, 0);
return refCount == 0;
}
OBJC_PUBLIC bool _Block_tryRetain(const void* arg)
{
struct Block_layout *self = block;
return (self->reserved) > 0 ? block : 0;
/* This is used by the weak reference management in ARC. The implementation
* follows the reasoning of `retain_fast()` in arc.mm: We want to abandon the
* retain operation if another thread has started deallocating the object between
* loading the weak pointer and executing the retain operation.
*/
struct Block_layout *block = (struct Block_layout*)arg;
int *refCountPtr = &block->reserved;
int refCountVal = __sync_fetch_and_add(refCountPtr, 0);
int newVal = refCountVal;
do {
refCountVal = newVal;
if (refCountVal <= 0)
{
return false;
}
newVal = __sync_val_compare_and_swap(refCountPtr, refCountVal, newVal + 1);
} while (newVal != refCountVal);
return true;
}

@ -0,0 +1,57 @@
/*
* Copyright (c) 2009 Remy Demarest
* Portions Copyright (c) 2009 David Chisnall
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef EMBEDDED_BLOCKS_RUNTIME
#import "objc/blocks_runtime.h"
#include "blocks_runtime.h"
#else
#import <Block.h>
#import <Block_private.h>
#endif
#include "visibility.h"
OBJC_PUBLIC const char *block_getType_np(const void *b)
{
return _Block_signature((void*)b);
}
/**
* Returns the block pointer, or NULL if the block is already
* being deallocated. The implementation does not employ atomic
* operations, so this function must only be called by the ARC
* subsystem after obtaining the weak-reference lock.
*/
PRIVATE void* block_load_weak(void *block)
{
struct Block_layout *self = block;
#ifdef EMBEDDED_BLOCKS_RUNTIME
return (self->reserved) > 0 ? block : 0;
#else
return (self->flags) & BLOCK_REFCOUNT_MASK ? block : 0;
#endif
}

@ -366,6 +366,11 @@ enum objc_class_flags
* On a class, guarantees that `+init` is trivial.
*/
objc_class_flag_fast_alloc_init = (1<<15),
/**
* The class is a block class. Reference count management must be done by
* the underlying blocks runtime.
*/
objc_class_flag_is_block = (1 << 16),
};
/**

@ -27,6 +27,7 @@ void init_gc(void);
void init_protocol_table(void);
void init_selector_tables(void);
void init_trampolines(void);
void init_early_blocks(void);
void objc_send_load_message(Class class);
void log_selector_memory_usage(void);
@ -76,6 +77,7 @@ static void init_runtime(void)
init_protocol_table();
init_class_tables();
init_alias_table();
init_early_blocks();
init_arc();
init_trampolines();
first_run = NO;

@ -3,7 +3,14 @@
#if defined(__clang__) && !defined(__OBJC_RUNTIME_INTERNAL__)
#pragma clang system_header
#endif
#ifdef __cplusplus
#define BLOCKS_EXPORT extern "C"
#else
#define BLOCKS_EXPORT extern
#endif
#include <stdbool.h>
#include "Availability.h"
/*
* This header file exposes some implementation details of the blocks runtime
@ -38,6 +45,23 @@ struct Block_descriptor
const char *encoding;
};
/**
* Checks whether the block is currently being deallocated.
*
* Used by ARC weak reference management. Only call this after the weak
* reference lock is acquired.
*/
OBJC_PUBLIC BLOCKS_EXPORT bool _Block_isDeallocating(const void *aBlock);
/**
* Atomically increments the reference count of the block.
* Returns true if the block was retained, and false if it is already
* being deallocated.
*
* Used by ARC weak reference management. Only call this after the weak
* reference lock is acquired.
*/
OBJC_PUBLIC BLOCKS_EXPORT bool _Block_tryRetain(const void *aBlock);
// Helper structure
struct Block_layout
{

Loading…
Cancel
Save