Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use libBlocksRuntime from swift-corelibs-libdispatch as the blocks runtime #293

Merged
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
2 changes: 1 addition & 1 deletion .cirrus.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
52 changes: 42 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ endif ()

INCLUDE (CheckCXXSourceCompiles)
INCLUDE (FetchContent)
INCLUDE (CheckSymbolExists)

set(libobjc_VERSION 4.6)

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the comment, it sounds as if things are broken if we don't have that symbol? Shouldn't we fail the build in that case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes absolutely. If forgot to make it fail hard when I remembered how, uhm, quirky the objc_constructInstance/objc_destructInstance API is that the legacy hook function would need.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, I am pondering doing the thing Apple does of embedding the refcount and weak flags in the isa pointer. I believe GNUstep should now all be clean to that.

It probably doesn’t make sense to do on 32-bit platforms so it would be 64-bit only, but would give us a nice memory saving there and would make it easier to support those APIs, at least on 64-bit platforms.

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>")

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -340,8 +365,12 @@ install(TARGETS ${INSTALL_TARGETS}

install(FILES ${libobjc_HDRS}
DESTINATION "${HEADER_INSTALL_PATH}/${INCLUDE_DIRECTORY}")
install(FILES ${libBlocksRuntime_COMPATIBILITY_HDRS}
DESTINATION "${HEADER_INSTALL_PATH}")

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")
Expand Down Expand Up @@ -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}")
Expand Down
14 changes: 14 additions & 0 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------

Expand Down
29 changes: 26 additions & 3 deletions NSBlocks.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
}
71 changes: 56 additions & 15 deletions arc.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -376,15 +384,15 @@ 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))
{
return;
}
_Block_release(obj);
return;
}
if (cls == static_cast<Class>(&_NSConcreteStackBlock))
{
return;
}
if (objc_test_class_flag(cls, objc_class_flag_fast_arc))
{
objc_release_fast_np(obj);
Expand Down Expand Up @@ -702,12 +710,24 @@ void deallocate(T* p, std::size_t)

}

#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
}

/**
Expand Down Expand Up @@ -843,9 +863,18 @@ static BOOL setObjectHasWeakRefs(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;
}
Expand Down Expand Up @@ -916,19 +945,31 @@ static BOOL setObjectHasWeakRefs(id obj)
return nil;
}
Class cls = classForObject(obj);
if (static_cast<Class>(&_NSConcreteMallocBlock) == cls)
if (objc_test_class_flag(cls, objc_class_flag_permanent_instances))
{
obj = static_cast<id>(block_load_weak(obj));
return obj;
}
else if (objc_test_class_flag(cls, objc_class_flag_permanent_instances))
else if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block)))
{
return obj;
obj = static_cast<id>(block_load_weak(obj));
if (obj == nil)
{
return nil;
}
// 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);
}
Expand Down
Loading
Loading