diff --git a/third_party/README b/third_party/README new file mode 100644 index 0000000000000000000000000000000000000000..67c19db3484468823310cf027ebc674573a257fd --- /dev/null +++ b/third_party/README @@ -0,0 +1,35 @@ +How to update libcoro +===================== + +cvs up + +How to update libev +=================== + +cvs up + +How to update luajit +==================== + +- download the latest release source tarball +- replace the contents of the luajit directory +with the contents of the tarball +- merge the original Makefile and the Makefile +from the new tarball. + +How to update libobjc2 +====================== + +How initial import was done: + +svn co http://svn.gna.org/svn/gnustep/libs/libobjc2/trunk/ ./libobjc + +find . -name '.svn' | xargs rm -rf +rm Makefile.clang +rm GNUMakefile + +How to update it: + +- delete GNUMakefile +- merge our Makefile with the Makefile in +the source tarball diff --git a/third_party/libobjc/ANNOUNCE b/third_party/libobjc/ANNOUNCE new file mode 100644 index 0000000000000000000000000000000000000000..75950a35df6ccc3b83932a6661a0c06686c69ab7 --- /dev/null +++ b/third_party/libobjc/ANNOUNCE @@ -0,0 +1,41 @@ +GNUstep Objective-C Runtime 1.6.1 +=============================== + +This is a point release to the seventh official release of the GNUstep +Objective-C runtime (a.k.a. libobjc2). This runtime was designed to support +the features of Objective-C 2 for use with GNUstep and other Objective-C +programs. Highlights of this release include: + +- Improved support for ARC autorelease pools. + +- Some small bug fixes in blocks support. + +- Improvements to the Objective-C++ unified exception model support. + +- Updated optimisation passes to work with LLVM 3.1 + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.6.1 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.6.1.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully backwards compatible +with the FSF's GCC 4.2.1 Objective-C ABI and also implements a new ABI that is +supported by Clang and Étoilé's LanguageKit and is required for some of the +newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is relatively new code and may still +contain bugs. If you come across any problems, please report them to the +GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/ANNOUNCE.1.0 b/third_party/libobjc/ANNOUNCE.1.0 new file mode 100644 index 0000000000000000000000000000000000000000..730984d6edc50e7d3e1d6b0eb46d208981df6acf --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.0 @@ -0,0 +1,33 @@ +GNUstep Objective-C Runtime 1.0 +=============================== + +This is the first official release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.0 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.0.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully compatible with the +FSF's GCC Objective-C ABI and also implements a new ABI that is supported by +Clang and is required for some of the newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is entirely new (MIT licensed) code and +may still contain bugs. If you come across any problems, please report them to +the GNUstep Developer mailing list <gnustep-dev@gnu.org>. A 1.1 release, +fixing any bugs that are encountered in wider deployment, is planned to +coincide with the next GNUstep release. diff --git a/third_party/libobjc/ANNOUNCE.1.1 b/third_party/libobjc/ANNOUNCE.1.1 new file mode 100644 index 0000000000000000000000000000000000000000..68d0a2f216a2c40cfcb17d4b9b178e360e2a2819 --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.1 @@ -0,0 +1,33 @@ +GNUstep Objective-C Runtime 1.1 +=============================== + +This is the second official release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. This release contains +minor bug fixes and provides compatibility with synthesised declared properties +from GCC 4.6 and recent versions of clang. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.1 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.1.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully compatible with the +FSF's GCC Objective-C ABI and also implements a new ABI that is supported by +Clang and is required for some of the newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is entirely new (MIT licensed) code and +may still contain bugs. If you come across any problems, please report them to +the GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/ANNOUNCE.1.2 b/third_party/libobjc/ANNOUNCE.1.2 new file mode 100644 index 0000000000000000000000000000000000000000..aa54c993cca1fbc3e86e8a2e2bb97d7dd3b00acb --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.2 @@ -0,0 +1,33 @@ +GNUstep Objective-C Runtime 1.2 +=============================== + +This is the 1.2 release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. This release contains +several bug fixes, and is tested with the current GNUstep trunk, so will be +compatible with the upcoming GNUstep release. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.2 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.2.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully compatible with the +FSF's GCC Objective-C ABI and also implements a new ABI that is supported by +Clang and is required for some of the newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is entirely new (MIT licensed) code and +may still contain bugs. If you come across any problems, please report them to +the GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/ANNOUNCE.1.3 b/third_party/libobjc/ANNOUNCE.1.3 new file mode 100644 index 0000000000000000000000000000000000000000..59d96f36b17e6b36df408058d245dfa9d01f7ae2 --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.3 @@ -0,0 +1,41 @@ +GNUstep Objective-C Runtime 1.3 +=============================== + +This is the fourth official release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. + +This release contains several bug fixes and includes a unified exception +model, providing the same features as Apple's Modern runtime for Objective-C++ +code, specifically the ability to throw Objective-C objects with @throw() or +throw() and catch them with @catch() or catch(). The new unified exception +model is supported by Clang 2.9 and is compatible with Apple's Objective-C++ +exception behaviour. Another enhancement in this release is the addition of +support for class aliases. This provides a run-time equivalent of +@compatibility_alias, allowing a class to show up in class lookup searching for +its alias. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.3 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.3.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully compatible with the +FSF's GCC Objective-C ABI and also implements a new ABI that is supported by +Clang and Étoilé's LanguageKit and is required for some of the newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is entirely new (MIT licensed) code and +may still contain bugs. If you come across any problems, please report them to +the GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/ANNOUNCE.1.4 b/third_party/libobjc/ANNOUNCE.1.4 new file mode 100644 index 0000000000000000000000000000000000000000..0a723a1ee94b610ea95fb5ea141f8d31e47886fb --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.4 @@ -0,0 +1,79 @@ +GNUstep Objective-C Runtime 1.4 +=============================== + +This is the fifth official release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. Highlights of this +release include: + +- Support for the associated reference APIs introduced with OS X 10.6. This + allows storing arbitrary objects associated with another object. + +- Concurrent, thread-safe, +initialize. The runtime will now send +initialize + messages to different classes concurrently in multiple threads, but still + ensures that no class receives another message until it has returned from + +initialize. This mirrors OS X behaviour. Care must be taken that + +initialize methods do not deadlock - if two classes are simultaneously + initialised from two different threads, and there +initialize methods call + methods in the other class, then deadlock will result. + +- Exceptions can now safely propagate out of +initialize methods. + +- Better hiding of local symbols. Now the internal runtime functions are not + visible from outside of the runtime. This may break code that attempts to + use private APIs, but means that it is now impossible to accidentally use + private APIs. + +- Dispatch table updates have been improved. Category loading now longer + triggers dtable creation and partial dtable updates are faster. + +- Improvements to the low memory profile. Uses 5-10% less (total) memory + running Gorm, and now passes the entire GNUstep and EtoileFoundation test + suites. Build with [g]make low_memory=yes to enable this mode. Note that + the low memory profile trades some CPU time for memory usage, so don't use it + for CPU-bound tasks. + +- The class lookup cache optimisation (LLVM) now caches lookups irrespective of + the ABI. It will insert direct references to the class structures if + possible (i.e. if the symbol is visible). If not, then it will cache the + result of objc_class_lookup(). The cache is shared across the module (the + library, if run as a link-time optimisation), so the lookup only needs to be + run once. This eliminates the need for explicit class lookup caching in the + source code (when using LLVM-based compilers, such as Clang, LanguageKit, or + DragonEgg). + +- Added some missing runtime API functions, such as those for setting and + getting instance variables. These are required by some language bridges, + although using them safely is not actually possible with the non-fragile ABI + (also true on OS X), since instance variables are no longer uniquely + identified by name. + +- Added support for accessing property type encodings. These are extended type + encodings, allowing code introspecting the properties to learn if they are + read-only, their assignment policy, the methods used to implement them, and + so on. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.4 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.4.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully compatible with the +FSF's GCC Objective-C ABI and also implements a new ABI that is supported by +Clang and Étoilé's LanguageKit and is required for some of the newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is entirely new (MIT licensed) code and +may still contain bugs. If you come across any problems, please report them to +the GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/ANNOUNCE.1.5 b/third_party/libobjc/ANNOUNCE.1.5 new file mode 100644 index 0000000000000000000000000000000000000000..bdf8d81d9350249c2d4b6ba3cc22a965b7dc8b71 --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.5 @@ -0,0 +1,55 @@ +GNUstep Objective-C Runtime 1.5 +=============================== + +This is the sixth official release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. Highlights of this +release include: + +- Support for Apple-compatible garbage collection APIs, along with extensions + to support CoreFoundation-style explicit reference counting in a garbage + collected environment. This uses the Boehm garbage collector and is enabled + by specifying boehm_gc=yes when building. This requires version 7.1 or later + of libgc. Code compiled with -fobjc-gc can be mixed with code that + implements normal reference counting and with code compiled with + -fobjc-gc-only. The runtime supports both GC and non-GC code when compiled + with GC support and will automatically select the correct behavior depending + on the loaded code. + +- The runtime will now use Boehm GC for several internal data structures, if it + is built with GC enabled. This avoids the need for defensive programming + with respect to thread safety in several places. + +- This is the first release to provide a superset of the functionality provided + by the Mac Objective-C runtime, as shipped with OS X 10.6. + +- Full support for Automatic Reference Counting (ARC), compatible with OS X + 10.7 and iOS 5, including support for __weak references. + +- The LLVM optimisation passes have been improved and better tested. Code + compiled with them now passes the EtoileFoundation test suite. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.5 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.5.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully compatible with the +FSF's GCC Objective-C ABI and also implements a new ABI that is supported by +Clang and Étoilé's LanguageKit and is required for some of the newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is entirely new (MIT licensed) code and +may still contain bugs. If you come across any problems, please report them to +the GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/ANNOUNCE.1.6 b/third_party/libobjc/ANNOUNCE.1.6 new file mode 100644 index 0000000000000000000000000000000000000000..618ab6ce8b47ecf8b28926d6203b3372d5684240 --- /dev/null +++ b/third_party/libobjc/ANNOUNCE.1.6 @@ -0,0 +1,64 @@ +GNUstep Objective-C Runtime 1.6 +=============================== + +This is the seventh official release of the GNUstep Objective-C runtime (a.k.a. +libobjc2). This runtime was designed to support the features of Objective-C 2 +for use with GNUstep and other Objective-C programs. Highlights of this +release include: + +- Compatibility with the new runtime APIs introduced with Mac OS X 10.7 / iOS 5. + +- Support for small objects (ones hidden inside a pointer). On 32-bit systems, + the runtime permits one small object class, on 64-bit systems it permits 4. + This is used by GNUstep for small NSNumber and NSString instances, and these + are used by LanguageKit for message sending to small integers. + +- Support for prototype-style object orientation. You can now add methods, as + well as associated references, to individual objects, and clone them. The + runtime now supports everything required for the JavaScript object model, + including the ability to use blocks as methods on x86, x86-64 and ARM. + +- Support for Apple-compatible objc_msgSend() functions for x86, x86-64, and + ARM. Using these approximately halves the cost of message sending operations + and results in a 10% smaller total binary size. + +- A fully maintained POSIX Makefile to make bootstrapping builds and packaging + easier. This will be used automatically if GNUstep Make is not installed. + +- Improvements to the included LLVM optimisation passes. Testing on a 2.8GHz + Xeon, a loop of 200,000,000 class messages took 0.8 seconds with all + optimisations enabled (including speculative inlining). With -Os, the test + took 2 seconds. With explicit IMP caching in the source code, the test took + 1.2 seconds. For reference, the same test using the GCC Objective-C runtime + took 11 seconds (when compiled with either Clang/LLVM or GCC). + +Various features of this release required some per-platform assembly code. For +the 1.6.0 release, ARM, x86 and x86-64 (with the SysV ABI, not with the Win64 +ABI) are supported. Future releases in the 1.6.x series will extend this to +other architectures. + +You may obtain the code for this release from subversion at the following +subversion branch: + +svn://svn.gna.org/svn/gnustep/libs/libobjc2/1.6 + +Alternatively, a tarball is available from: + +http://download.gna.org/gnustep/libobjc2-1.6.tar.bz2 + +The runtime library is responsible for implementing the core features of the +object model, as well as exposing introspection features to the user. The +GNUstep runtime implements Apple's Objective-C Runtime APIs, and a small number +of GCC APIs for legacy compatibility. + +This library is based on the Étoilé Objective-C Runtime, an earlier research +prototype, and includes support for non-fragile instance variables, +type-dependent dispatch, and object planes. It is fully backwards compatible +with the FSF's GCC 4.2.1 Objective-C ABI and also implements a new ABI that is +supported by Clang and Étoilé's LanguageKit and is required for some of the +newer features. + +Although the runtime has been tested by several people, and is being used +extensively by the Étoilé project, it is relatively new code and may still +contain bugs. If you come across any problems, please report them to the +GNUstep Developer mailing list <gnustep-dev@gnu.org>. diff --git a/third_party/libobjc/API b/third_party/libobjc/API new file mode 100644 index 0000000000000000000000000000000000000000..a65c0be395da8369166212fcc0c3e6e670c19813 --- /dev/null +++ b/third_party/libobjc/API @@ -0,0 +1,134 @@ +GNUstep Runtime APIs +==================== + +The GNUstep Objective-C runtime aims to implement all of the APIs defined in +Apple's Objective-C Runtime Reference. That document should be taken as the +authoritative reference to the majority of the APIs exposed by this runtime. +Any discrepancies between the implementations in this runtime and the +documentation should be regarded as a bug in the runtime. + +In addition to these APIs, the runtime also exposes some non-portable APIs. +These are all marked as OBJC_NONPORTABLE in the headers. Many of these APIs +are also implemented in GNUstep's ObjectiveC2 framework, which uses the GCC +Objective-C runtime for the underlying implementation and provides a portable +set of APIs on top of the GCC runtime structures and functions. + +Runtime Capabilities +-------------------- + +The objc/capabilities.h header defines runtime capabilities. A copy of this +header is also present in the ObjectiveC2 framework, so you can conditionally +include either version. + +You can perform two sorts of checks using this header. Constants declared in +the header describe capabilities that the runtime may or may not implement. If +a capability is missing from this header, then it means that you are using an +old version of the runtime, which lacks any knowledge of the header. You can +use this to refuse to compile with old runtime versions. + +You can also use the objc_test_capability() function to test whether a +particular capability is present at run time. Several of the capabilities are +optional in this runtime, and may not be compiled in to a given install. If +you require a particular capability, you can use the OBJC_REQUIRE_CAPABILITY() +macro to insert a load function that will abort if the capability is not present. + +This shows how to refuse to compile or run with versions of the runtime that do +not support type-dependent dispatch: + + #ifndef OBJC_CAP_TYPE_DEPENDENT_DISPATCH + # error Type-dependent dispatch support required! + #else + OBJC_REQUIRE_CAPABILITY(OBJC_CAP_TYPE_DEPENDENT_DISPATCH); + #endif + +Typed Selectors +--------------- + +Like the GCC runtime, this runtime uses typed selectors. In recent versions, +message lookup is also dependent on the type of the selector by default. This +can be disabled by not defining the TYPE_DEPENDENT_DISPATCH macro when +building. When using GNU make, you can get name-dependent dispatch by doing: + + $ gmake tdd=no + +This is strongly discouraged. It will give compatibility with the semantics of +the NeXT, Apple, and GCC runtimes, however these semantics include random stack +corruption from valid code. + +There are three functions for dealing with typed selectors. The first two are +direct equivalents of other functions. + + SEL sel_registerTypedName_np(const char *selName, const char *types); + +sel_registerName() will register an untyped selector. This variant registers a +typed selector, using the specified name and type encoding. + + const char *sel_getType_np(SEL aSel); + +This function simply returns the type encoding of the given selector, or NULL for a typed selector. + + unsigned sel_copyTypes_np(const char *selName, + const char **types, + unsigned count); + +This function copies *all* of the type encodings for a given selector name. +Generally, there are not many of these. In a well-written program, there will +be exactly one type encoding for a given selector. In a typical program, there +will be 1-3. It is not worth allocating a buffer on the heap for most cases, +so this function is designed to take a stack buffer. + +Unlike other functions in the runtime, this always returns the total number of +type encodings, not the number that were found. This means that you can call +it once with a smallish on-stack buffer and then call it again with a +malloc()'d buffer if there are a lot of encodings for a specific selector, as +follows. + + char *t[16]; + char *types = t; + unsigned total = sel_copyTypes_np("alloc", types, total); + if (total > 16) + { + types = calloc(sizeof(char*), total); + sel_copyTypes_np("alloc", types, total); + } + // Do stuff with the types. + if (total > 16) + { + free(types); + } + + +**Note**: This runtime does not provide any equivalent of the GCC runtime's +sel_get_typed_uid() or sel_get_any_typed_uid(). This is intentional. It is +impossible to use these functions correctly and they should never have been +made part of the public API. + +Message Sending +--------------- + +For ABI compatibility with the GCC runtime, this runtime implements the +objc_msg_lookup() and objc_msg_lookup_super() functions used to implement +message sending. + +The new ABI uses the objc_msg_lookup_sender() function in place of +objc_msg_lookup(). This allows fast proxies and caching of the lookup result +at the callsite. You can find more a detailed explanation of how this works in +the README file. + +This runtime also provides this semi-private function, which can be of use in +implementing parts of the Foundation framework and similar low-level libraries: + + struct objc_slot* objc_get_slot(Class cls, SEL selector); + +This looks up the slot for a given selector, without invoking any forwarding +mechanisms. This is most useful for quickly finding the type encoding of a +method (e.g. for implementing forwarding). The lookup is as fast as a normal +message lookup, and the types field of the returned slot provides the type +encoding. This is significantly faster than using class_getInstanceMethod(), +which needs to perform a linear search along a list (O(1) vs O(n)). + +Hooks +----- + +All of the callback hooks provided by this runtime are described in +objc/hooks.h. diff --git a/third_party/libobjc/COPYING b/third_party/libobjc/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..8647a56b739dfc73c8501a1a06ecbd7e75f737e4 --- /dev/null +++ b/third_party/libobjc/COPYING @@ -0,0 +1,20 @@ +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. + diff --git a/third_party/libobjc/NSBlocks.m b/third_party/libobjc/NSBlocks.m new file mode 100644 index 0000000000000000000000000000000000000000..32870da35aa4fdf937c52da4f861f512504c60ec --- /dev/null +++ b/third_party/libobjc/NSBlocks.m @@ -0,0 +1,52 @@ +#import "objc/runtime.h" +#import "class.h" +#import "lock.h" +#import "objc/blocks_runtime.h" +#import "dtable.h" +#include <assert.h> + +struct objc_class _NSConcreteGlobalBlock; +struct objc_class _NSConcreteStackBlock; +struct objc_class _NSConcreteMallocBlock; + +static struct objc_class _NSConcreteGlobalBlockMeta; +static struct objc_class _NSConcreteStackBlockMeta; +static struct objc_class _NSConcreteMallocBlockMeta; + +static struct objc_class _NSBlock; +static struct objc_class _NSBlockMeta; + +static void createNSBlockSubclass(Class superclass, Class newClass, + Class metaClass, char *name) +{ + // Initialize the metaclass + //metaClass->class_pointer = superclass->class_pointer; + //metaClass->super_class = superclass->class_pointer; + metaClass->info = objc_class_flag_meta; + metaClass->dtable = uninstalled_dtable; + + // Set up the new class + newClass->isa = metaClass; + newClass->super_class = (Class)superclass->name; + newClass->name = name; + newClass->info = objc_class_flag_class; + newClass->dtable = uninstalled_dtable; + + LOCK_RUNTIME_FOR_SCOPE(); + class_table_insert(newClass); + +} + +#define NEW_CLASS(super, sub) \ + createNSBlockSubclass(super, &sub, &sub ## Meta, #sub) + +BOOL objc_create_block_classes_as_subclasses_of(Class super) +{ + if (_NSBlock.super_class != NULL) { return NO; } + + NEW_CLASS(super, _NSBlock); + NEW_CLASS(&_NSBlock, _NSConcreteStackBlock); + NEW_CLASS(&_NSBlock, _NSConcreteGlobalBlock); + NEW_CLASS(&_NSBlock, _NSConcreteMallocBlock); + return YES; +} diff --git a/third_party/libobjc/Protocol2.m b/third_party/libobjc/Protocol2.m new file mode 100644 index 0000000000000000000000000000000000000000..7897b4387978339d9f9857de3cfbebc92d0d6464 --- /dev/null +++ b/third_party/libobjc/Protocol2.m @@ -0,0 +1,34 @@ +#include "objc/runtime.h" +#include "protocol.h" +#include "class.h" +#include <stdio.h> +#include <string.h> + +@implementation Protocol +// FIXME: This needs removing, but it's included for now because GNUstep's +// implementation of +[NSObject conformsToProtocol:] calls it. +- (BOOL)conformsTo: (Protocol*)p +{ + return protocol_conformsToProtocol(self, p); +} +- (id)retain +{ + return self; +} +- (void)release {} ++ (Class)class { return self; } +- (id)self { return self; } +@end +@implementation Protocol2 @end + +/** + * This class exists for the sole reason that the legacy GNU ABI did not + * provide a way of registering protocols with the runtime. With the new ABI, + * every protocol in a compilation unit that is not referenced should be added + * in a category on this class. This ensures that the runtime sees every + * protocol at least once and can perform uniquing. + */ +@interface __ObjC_Protocol_Holder_Ugly_Hack { id isa; } @end +@implementation __ObjC_Protocol_Holder_Ugly_Hack @end + +@implementation Object @end diff --git a/third_party/libobjc/README b/third_party/libobjc/README new file mode 100644 index 0000000000000000000000000000000000000000..ec106c09a92d4ece035258dd24aa4954e672adc4 --- /dev/null +++ b/third_party/libobjc/README @@ -0,0 +1,464 @@ +GNUstep Objective-C Runtime +=========================== + +The GNUstep Objective-C runtime is designed as a drop-in replacement for the +GCC runtime. It supports both a legacy and a modern ABI, allowing code +compiled with old versions of GCC to be supported without requiring +recompilation. The modern ABI adds the following features: + +- Non-fragile instance variables. +- Protocol uniquing. +- Object planes support. +- Declared property introspection. + +Both ABIs support the following feature above and beyond the GCC runtime: + +- The modern Objective-C runtime APIs, introduced with OS X 10.5. +- Blocks (closures). +- Low memory profile for platforms where memory usage is more important than + speed. +- Synthesised property accessors. +- Efficient support for @synchronized() +- Type-dependent dispatch, eliminating stack corruption from mismatched + selectors. +- Support for the associated reference APIs introduced with Mac OS X 10.6. +- Support for the automatic reference counting APIs introduced with Mac OS X + 10.7 + +History +------- + +Early work on the GNUstep runtime combined code from the GCC Objective-C +runtime, the Étoilé Objective-C runtime, Remy Demarest's blocks runtime for OS +X 10.5, and the Étoilé Objective-C 2 API compatibility framework. All of these +aside from the GCC runtime were MIT licensed, although the GPL'd code present +in the GCC runtime meant that the combined work had to remain under the GPL. + +Since then, all of the GCC code has been removed, leaving the remaining files +all MIT licensed, and allowing the entire work to be MIT licensed. + +The exception handling code uses a header file implementing the generic parts +of the Itanium EH ABI. This file comes from PathScale's libcxxrt. PathScale +kindly allowed it to be MIT licensed for inclusion here. + +Non-Fragile Instance Variables +------------------------------ + +When a class is compiled to support non-fragile instance variables, the +instance_size field in the class is set to 0 - the size of the instance +variables declared on that class (excluding those inherited. For example, an +NSObject subclass declaring an int ivar would have its instance_size set to 0 - +sizeof(int)). The offsets of each instance variable in the class's ivar_list +field are then set to the offset from the start of the superclass's ivars. + +When the class is loaded, the runtime library uses the size of the superclass +to calculate the correct size for this new class and the correct offsets. Each +instance variable should have two other variables exported as global symbols. +Consider the following class: + +@interface NewClass : SuperClass { + int anIvar; +} +@end + +This would have its instance_size initialized to 0-sizeof(int), and anIvar's +offset initialized to 0. It should also export the following two symbols: + +int __objc_ivar_offset_value_NewClass.anIvar; +int *__objc_ivar_offset_NewClass.anIvar; + +The latter should point to the former or to the ivar_offset field in the ivar +metadata. The former should be pointed to by the only element in the +ivar_offsets array in the class structure. + +In other compilation units referring to this ivar, the latter symbol should be +exported as a weak symbol pointing to an internal symbol containing the +compiler's guess at the ivar offset. The ivar will then work as a fragile ivar +when NewClass is compiled with the old ABI. If NewClass is compiled with the +new ABI, then the linker will replace the weak symbol with the version in the +class's compilation unit and references which use this offset will function +correctly. + +If the compiler can guarantee that NewClass is compiled with the new ABI, for +example if it is declared in the same compilation unit, by finding the symbol +during a link-time optimization phase, or as a result of a command-line +argument, then it may use the __objc_ivar_offset_value_NewClass.anIvar symbol +as the ivar offset. This eliminates the need for one load for every ivar +access. + +Protocols +--------- + +The runtime now provides a __ObjC_Protocol_Holder_Ugly_Hack class. All +protocols that are referenced but not defined should be registered as +categories on this class. This ensures that every protocol is registered with +the runtime. + +In the near future, the runtime will ensure that protocols can be looked up by +name at run time and that empty protocol definitions have their fields updated +to match the defined version. + +Protocols have been extended to provide space for introspection on properties +and optional methods. These fields only exist on protocols compiled with a +compiler that supports Objective-C 2. To differentiate the two, the isa +pointer for new protocols will be set to the Protocol2 class. + +Blocks +------ + +The GNUstep runtime provides the run time support required for Apple's blocks +(closures) extension to C. This follows the same ABI as OS X 10.6. + +Fast Proxies and Cacheable Lookups +---------------------------------- + +The new runtime provides two mechanisms for faster lookup. The older +Vobjc_msg_lookup() function, which returns an IMP, is still supported, however +it is no longer recommended. The new lookup functions is: + +Slot_t objc_msg_lookup_sender(id *receiver, SEL selector, id sender) + +The receiver is passed by pointer, and so may be modified during the lookup +process. The runtime itself will never modify the receiver. The following +hook is provided to allow fast proxy support: + +id (*objc_proxy_lookup)(id receiver, SEL op); + +This function takes an object and selector as arguments and returns a new +objects. The lookup will then be re-run and the final message should be sent to +the new object. + +The returned Slot_t from the new lookup function is a pointer to a structure +which contains both an IMP and a version (among other things). The version is +incremented every time the method is overridden, allowing this to be cached by +the caller. User code wishing to perform IMP caching may use the old mechanism +if it can guarantee that the IMP will not change between calls, or the newer +mechanism. Note that a modern compiler should insert caching automatically, +ideally with the aid of run-time profiling results. To support this, a new hook +has been added: + +Slot_t objc_msg_forward3(id receiver, SEL op); + +This is identical to objc_msg_forward2(), but returns a pointer to a slot, +instead of an IMP. The slot should have its version set to 0, to prevent +caching. + +Object Planes +------------- + +Object planes provide interception points for messages between groups of +related objects. They can be thought of as similar to processes, with mediated +inter-plane communication. A typical use-case for an object plane is to +automatically queue messages sent to a thread, or to record every message sent +to model objects. Planes can dramatically reduce the number of proxy objects +required for this kind of activity. + +The GNUstep runtime adds a flag to class objects indicating that their +instances are present in the global plane. All constant strings, protocols, +and classes are in the global plane, and may therefore be sent and may receive +messages bypassing the normal plane interception mechanism. + +The runtime library does not provide direct support for planes, it merely +provides the core components required to implement support for planes in +another framework. Two objects are regarded as being in the same plane when +they words immediately before their isa pointers are the same. In this case, +the runtime's usual dispatch mechanisms will be used. In all other cases, the +runtime will delegate message lookup to another library via the following hook: + +Slot_t (*objc_plane_lookup)(id *receiver, SEL op, id sender); + +From the perspective of the runtime, the plane identifier is opaque. In +GNUstep, it is a pointer to an NSZone structure. + +Threading +--------- + +The threading APIs from GCC libobjc are not present in this runtime. They were +buggy, badly supported, inadequately tested, and irrelevant now that there are +well tested thread abstraction layers, such as the POSIX thread APIs and the +C1x thread functions. The library always runs in thread-safe mode. The same +functions for locking the runtime mutex are still supported, but their use any +mutex not exported by the runtime library is explicitly not supported. The +(private) lock.h header is used to abstract the details of different threading +systems sufficiently for the runtime. This provides mechanisms for locking, +unlocking, creating, and destroying mutex objects. + +Type-Dependent Dispatch +----------------------- + +Traditionally, Objective-C method lookup is done entirely on the name of the +method. This is problematic when the sender and receiver of the method +disagree on the types of a method. + +For example, consider a trivial case where you have two methods with the same +name, one taking an integer, the other taking a floating point value. Both +will pass their argument in a register on most platforms, but not the same +register. If the sender thinks it is calling one, but is really calling the +other, then the receiver will look in the wrong register and use a nonsense +value. The compiler will often not warn about this. + +This is a relatively benign example, but if the mismatch is between methods +taking or returning a structure and those only using scalar arguments and +return then the call frame layout will be so different that the result will be +stack corruption, possibly leading to security holes. + +If you compile the GNUstep runtime with type-dependent dispatch enabled, then +sending a message with a typed selector will only ever invoke a method with the +same types. Sending a message with an untyped selector will invoke any method +with a matching name, although the slot returned from the lookup function will +contain the types, allowing the caller to check them and construct a valid call +frame, if required. + +If a lookup with a typed selector matches a method with the wrong types, the +runtime will call a handler. This handler, by default, prints a helpful +message and exits. LanguageKit provides an alternative version which +dynamically generates a new method performing the required boxing and calling +the original. + +Exception ABI Changes +--------------------- + +The non-fragile ABI makes a small change to the exception structure. The old +GCC ABI was very poorly designed. It attempted to do things in a clever way, +avoiding the need to call functions on entering and exiting catch blocks, but +in doing so completely broke support for throwing foreign (e.g. C++) exceptions +through Objective-C stack frames containing an @finally or @catch(id) block. +It also could not differentiate between @catch(id) (catch any object type) and +@catch(...) (catch any exception and discard it). + +The new ABI makes a small number of changes. Most importantly, @catch(id) is +now indicated by using the string "@id" as the type ID, in the same way that +"Foo" is used for @catch(Foo*). Catchalls remain identified by a NULL pointer +in this field, as with the GCC ABI. The runtime will still deliver the +exception object to catchalls, for interoperability with old code and with the +old ABI, but this comes with all of the same problems that the old ABI had +(i.e. code using this ABI will break in exciting and spectacular ways when +mixed with C++). + +The runtime provides a hook, _objc_class_for_boxing_foreign_exception(), which +takes an exception class (64-bit integer value) as an argument, and returns a +class for boxing exceptions using this ABI. The returned class must implement +a +exceptionWithForeignException: method, taking a pointer to the ABI-defined +generic exception structure as the argument. It should also implement a +-rethrow method, used for rethrowing the exception. If this is omitted, then +the boxed version of the exception, rather than the original, will propagate +out from @finally blocks. + +If a catch block exists that handles this class, then it will box foreign +exceptions and allow them to propagate through any @finally directives. Boxed +exceptions will only match explicit catch statements. To fully understand the +semantics, we'll take a look at some examples. These all use GNUstep's +`CXXException` class, which boxes C++ exceptions, and this simple C++ function: + + extern "C" void throwcxx() + { + throw 1; + } + +This exception will be caught, boxed, and then silently discarded by a catchall: + + @try + { + throwcxx(); + } + @catch(...) + { + // This will be reached, then the exception propagation stops. + } + +If an id catch block is encountered, it will be ignored, but @finally blocks +will still be called: + + @try + { + throwcxx(); + } + @catch(id anyObject) + { + // Code here is not reached. + } + @finally + { + // This will be reached, then the exception propagation continues. + } + +The `CXXException` class is a subclass of `NSObject`, but catch statements for +the superclass will not be hit: + + @try + { + throwcxx(); + } + @catch(NSObject *anyObject) + { + // Code here is not reached. + } + @catch(CXXException *boxedForeign) + { + // Code here is reached. + } + +As of version 1.3, the runtime also provides a unified exception model for +Objective-C++. This allows C++ `catch` statements and Objective-C `@catch` +statements to catch Objective-C objects thrown with `@throw` or `throw`. + +This required some small changes to the ABI. Both `@try` and `try` must use +the same personality function in Objective-C++ code, because otherwise things +like nested blocks are not possible. The unwind library must be able to map +from any instruction pointer value to a single personality function, and +without a unified personality function, it would not be able to in code like +this: + + @try + { + try + { + // What personality function should be used when unwinding from + // here? + objc_exception_throw(@"foo"); + } + catch (int i) {} + } + catch (id foo) {} + +If there is a single personality function, there must be a single format for +language-specific data. The current C++ format is more expressive than the +Objective-C format, so we used it directly, with one extension. C++ landing +pads are identified by a `std::type_info` subclass in the type info tables for +exception unwinding. We provide two subclasses of this: + + gnustep::libobjc::__objc_id_type_info; + gnustep::libobjc::__objc_class_type_info; + +The first is used for identifying `id`-typed throws and catches. The second is +for identifying Objective-C classes. All `id` throws use the singleton +instance of the first class, exported as `__objc_id_type_info` (with C +linkage). Type info for classes should generate an instance of the second +class, with the name `__objc_eh_typeinfo_Foo` where `Foo` is the name of the +class (e.g. `NSObject` should generate `__objc_eh_typeinfo_NSObject`). The +name field should be set to the name of the class, via a global variable named +`__objc_eh_typename_Foo`. Both should have link-once ODR linkage, so that the +linker will ensure that they are unique and pointer comparison can be used to +test for equality (required by the C++ personality function). + +In Objective-C++ code, the personality function is: + + __gnustep_objcxx_personality_v0() + +This is a very thin wrapper around the C++ personality function. If it is +called with an exception coming from Objective-C code, then it wraps it in a +__cxa_exception structure (defined by the C++ ABI spec). For any other +exception type (including C++ exceptions), it passes it directly to the C++ +personality function. + +The Objective-C personality function was also modified slightly so that any +incoming C++ exception that was has type info indicating that it's an +Objective-C type is treated as an Objective-C object, for the purpose of +exception delivery. + +Low Memory Profile +------------------ + +The dispatch tables for each class, in spite of using a more space-efficient +sparse array implementation than GCC libobjc, can still use quite a lot of +memory. The NeXT runtime avoided this problem by not providing dispatch tables +at all. Instead, it did a linear search of the method lists, caching a few +results. Although this is O(n), it performed reasonably well. Most message +sends are to a small number of methods. For example, an NSMutableDictionary is +most often sent -objectForKey: and -setObject:forKey:. If these two methods +are in the cache, then the O(n) algorithm is almost never used. + +The GNUstep runtime's low memory profile stores the slots in a sorted array. +This means that the worst case performance is O(log(n)) in terms of the number +of methods, as the uncached lookup proceeds using a binary search. + +If you compile the GNUstep runtime with the low memory profile, it uses a +similar strategy. The caches use the same slot-caching mechanism described +above and can be combined with caching at the call site. The runtime will not +create dispatch tables, which can save a few MB of RAM in projects with a lot +of classes, but can make message sending a lot slower in the worst case. + +To enable the low memory profile, add low_memory=yes to your make command line. + +Objective-C 2 Features +---------------------- + +The runtime now provides implementations of the functions required for the +@synchronized directive, for property accessors, and for fast enumeration. The +public runtime function interfaces now match those of OS X. + +Garbage Collection +------------------ + +As of version 1.5, the runtime support Apple-compatible garbage collection +semantics. The `objc/objc-auto.h` header describes the interfaces to garbage +collection. This contains some extensions to Apple's API, required to +implement parts of Cocoa that rely on private interfaces between Cocoa, Apple +libobjc, and Autozone. + +Garbage collection is implemented using the Boehm-Demers-Weiser garbage +collector. If built with boehm_gc=no, this support will not be compiled into +the runtime. When built with GC support, the runtime will use garbage +collection for its internal tables, irrespective of whether linked Objective-C +code is using it. + +Zeroing weak references are implemented by storing the bit-flipped version of +the value (making it invisible to the collector) at the designated address. +The read barrier reads the value while holding the collector lock. This +ensures that reads of weak variables never point to finalised objects. + +The runtime uses the objc_assign_global() write barrier to add static roots. +Currently, GNUstep crashes if the collector relies on every write of a pointer +to a static location being through this write barrier, so this requirement is +relaxed. It will be enabled at some point in the future. + +Several environment variables can be used for debugging programs + +- LIBOBJC_DUMP_GC_STATUS_ON_EXIT. I this is set, then the program will dump + information about the garbage collector when it exits. +- LIBOBJC_DUMP_GC_STATUS_ON_SIGNAL. This should be set to a signal number. + The program will dump GC statistics when it receives the corresponding signal + (SIGUSR2 if this environment variable is set to something that is not a + number). +- LIBOBJC_LOG_ALLOCATIONS. This may be set to the name of a file. The runtime + will dump a stack trace on every allocation and finalisation to the named + file. This can be used to implement tools like Apple's malloc_history(). + Note: Enabling this causes a significant speed decrease. +- LIBOBJC_CANARIES. If this environment variable is set, then every allocation + of garbage-collected memory will have a canary value appended to it. On + finalisation, the runtime will check that this value has not been modified, + and abort if it has. This can help to catch heap buffer overflows. It is + most useful when debugging. + +Automatic Reference Counting +---------------------------- + +As of version 1.5, the runtime provides support for automatic reference +counting (ARC). This uses the same runtime interface as documented by the ABI +supplement here: + +http://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime + +The runtime implements the following optimisations: + +- Classes that have ARC-compliant retain, release, and autorelease methods will + never see them called from ARC code. Instead, equivalent code will be run + directly. +- If an object is autoreleased, returned, and retained, it is just stored in + thread-local storage temporarily, not actually autoreleased. +- Moving weak references skips the retain / release step. + +ARC requires the ability to interoperate perfectly with manual retain / release +code, including the ability for non-ARC code to implement custom reference +counting behaviour. If an object implements -_ARCCompliantRetainRelease, then +it is advertising that its retain, release, and autorelease implementations are +ARC-compatible. These methods may be called explicitly in non-ARC code, but +will not be called from ARC. + +ARC moves autorelease pools into the runtime. If NSAutoreleasePool exists and +does not implement a -_ARCCompatibleAutoreleasePool method, then it will be +used directly. If it does not exist, ARC will implement its own autorelease +pools. If it exists and does implement -_ARCCompatibleAutoreleasePool then it +must call objc_autoreleasePoolPush() and objc_autoreleasePoolPop() to manage +autoreleased object storage and call objc_autorelease() in its -addObject: +method. diff --git a/third_party/libobjc/Test/BlockImpTest.m b/third_party/libobjc/Test/BlockImpTest.m new file mode 100644 index 0000000000000000000000000000000000000000..f43a040f38e909162b3444ee89a7514ea7df72fa --- /dev/null +++ b/third_party/libobjc/Test/BlockImpTest.m @@ -0,0 +1,54 @@ +#include <objc/runtime.h> +#include <objc/blocks_runtime.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +struct big +{ + int a, b, c, d, e; +}; + +@interface Foo @end +@implementation Foo @end +@interface Foo (Dynamic) ++(int)count: (int)i; ++(struct big)sret; +@end + +int main(void) +{ + __block int b = 0; + void* blk = ^(id self, int a) { + b += a; + return b; }; + blk = Block_copy(blk); + IMP imp = imp_implementationWithBlock(blk); + char *type = block_copyIMPTypeEncoding_np(blk); + assert(NULL != type); + class_addMethod((objc_getMetaClass("Foo")), @selector(count:), imp, type); + free(type); + assert(2 == [Foo count: 2]); + assert(4 == [Foo count: 2]); + assert(6 == [Foo count: 2]); + assert(imp_getBlock(imp) == (blk)); + imp_removeBlock(blk); + + blk = ^(id self) { + struct big b = {1, 2, 3, 4, 5}; + return b; + }; + imp = imp_implementationWithBlock(blk); + assert(imp && "Can't make sret IMP"); + type = block_copyIMPTypeEncoding_np(blk); + assert(NULL != type); + class_addMethod((objc_getMetaClass("Foo")), @selector(sret), imp, type); + free(type); + struct big s = [Foo sret]; + assert(s.a == 1); + assert(s.b == 2); + assert(s.c == 3); + assert(s.d == 4); + assert(s.e == 5); + return 0; +} diff --git a/third_party/libobjc/Test/GNUmakefile b/third_party/libobjc/Test/GNUmakefile new file mode 100644 index 0000000000000000000000000000000000000000..94e24fdc1533b49ec334e38c1b26cd5d893676d1 --- /dev/null +++ b/third_party/libobjc/Test/GNUmakefile @@ -0,0 +1,8 @@ +include $(GNUSTEP_MAKEFILES)/common.make + +TOOL_NAME = RuntimeTest + +RuntimeTest_OBJC_FILES=\ + RuntimeTest.m + +include $(GNUSTEP_MAKEFILES)/tool.make diff --git a/third_party/libobjc/Test/PropertyIntrospectionTest.m b/third_party/libobjc/Test/PropertyIntrospectionTest.m new file mode 100644 index 0000000000000000000000000000000000000000..bcf82b74467b61269fcf2ba08a067d11313056ff --- /dev/null +++ b/third_party/libobjc/Test/PropertyIntrospectionTest.m @@ -0,0 +1,36 @@ +#import <objc/runtime.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +@interface Foo +@property (getter=bar, setter=setBar:, nonatomic, copy) id foo; +@end +@interface Foo(Bar) +-(id)bar; +-(void)setBar:(id)b; +@end +@implementation Foo +@synthesize foo; +@end + +int main(void) +{ + objc_property_t p = class_getProperty(objc_getClass("Foo"), "foo"); + unsigned int count; + objc_property_attribute_t *l = property_copyAttributeList(p, &count); + for (unsigned int i=0 ; i<count ; i++) + { + switch (l[i].name[0]) + { + case 'T': assert(0==strcmp(l[i].value, "@")); break; + case 'C': assert(0==strcmp(l[i].value, "")); break; + case 'N': assert(0==strcmp(l[i].value, "")); break; + case 'G': assert(0==strcmp(l[i].value, "bar")); break; + case 'S': assert(0==strcmp(l[i].value, "setBar:")); break; + case 'B': assert(0==strcmp(l[i].value, "foo")); break; + } + } + assert(0 == property_copyAttributeList(0, &count)); + return 0; +} diff --git a/third_party/libobjc/Test/ProtocolCreation.m b/third_party/libobjc/Test/ProtocolCreation.m new file mode 100644 index 0000000000000000000000000000000000000000..204021d88f33ede34c8f7ec921dd44f9e2ae4a3a --- /dev/null +++ b/third_party/libobjc/Test/ProtocolCreation.m @@ -0,0 +1,28 @@ +#import <objc/runtime.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +@protocol Test2 @end + +int main(void) +{ + Protocol *p = objc_allocateProtocol("Test"); + protocol_addMethodDescription(p, @selector(someMethod), "@:", YES, NO); + assert(objc_getProtocol("Test2")); + protocol_addProtocol(p, objc_getProtocol("Test2")); + objc_property_attribute_t attrs[] = { {"T", "@" } }; + protocol_addProperty(p, "foo", attrs, 1, YES, YES); + objc_registerProtocol(p); + Protocol *p1 = objc_getProtocol("Test"); + assert(p == p1); + struct objc_method_description d = protocol_getMethodDescription(p1, @selector(someMethod), YES, NO); + assert(strcmp(sel_getName(d.name), "someMethod") == 0); + assert(strcmp((d.types), "@:") == 0); + assert(protocol_conformsToProtocol(p1, objc_getProtocol("Test2"))); + unsigned int count; + objc_property_t *props = protocol_copyPropertyList(p1, &count); + assert(count == 1); + assert(strcmp("T@,Vfoo", property_getAttributes(*props)) == 0); + return 0; +} diff --git a/third_party/libobjc/Test/RuntimeTest.m b/third_party/libobjc/Test/RuntimeTest.m new file mode 100644 index 0000000000000000000000000000000000000000..83eeb05b3bde58ca4684f37a8b041b3a9cf2c3d9 --- /dev/null +++ b/third_party/libobjc/Test/RuntimeTest.m @@ -0,0 +1,298 @@ +#import <Foundation/Foundation.h> +#include <objc/runtime.h> + +static int exitStatus = 0; + +static void _test(BOOL X, char *expr, int line) +{ + if (!X) + { + exitStatus = 1; + fprintf(stderr, "ERROR: Test failed: '%s' on %s:%d\n", expr, __FILE__, line); + } +} +#define test(X) _test(X, #X, __LINE__) + +static int stringsEqual(const char *a, const char *b) +{ + return 0 == strcmp(a,b); +} + + + +@interface Foo : NSObject +{ + id a; +} +- (void) aMethod; ++ (void) aMethod; +- (int) manyTypes; +- (void) synchronizedCode; ++ (void) synchronizedCode; ++ (id) shared; +- (BOOL) basicThrowAndCatchException; +@end + +@interface Bar : Foo +{ + id b; +} +- (void) anotherMethod; ++ (void) anotherMethod; +- (id) manyTypes; +- (id) aBool: (BOOL)d andAnInt: (int) w; +@end + + +@implementation Foo +- (void) aMethod +{ +} ++ (void) aMethod +{ +} +- (int) manyTypes +{ + return YES; +} +- (void) synchronizedCode +{ + @synchronized(self) { [[self class] synchronizedCode]; } +} ++ (void) synchronizedCode +{ + @synchronized(self) { } +} ++ (id) shared +{ + @synchronized(self) { } + return nil; +} +- (void) throwException +{ + @throw [NSException exceptionWithName: @"RuntimeTestException" reason: @"" userInfo: nil]; +} +- (BOOL) basicThrowAndCatchException +{ + @try + { + [self throwException]; + } + @catch (NSException *e) + { + NSLog(@"Caught %@", e); + } + @finally + { + return YES; + } + return NO; +} +@end + +@implementation Bar +- (void) anotherMethod +{ +} ++ (void) anotherMethod +{ +} +- (id) manyTypes +{ + return @"Hello"; +} +- (id) aBool: (BOOL)d andAnInt: (int) w +{ + return @"Hello"; +} +@end + + +void testInvalidArguments() +{ + test(NO == class_conformsToProtocol([NSObject class], NULL)); + test(NO == class_conformsToProtocol(Nil, NULL)); + test(NO == class_conformsToProtocol(Nil, @protocol(NSCoding))); + test(NULL == class_copyIvarList(Nil, NULL)); + test(NULL == class_copyMethodList(Nil, NULL)); + test(NULL == class_copyPropertyList(Nil, NULL)); + test(NULL == class_copyProtocolList(Nil, NULL)); + test(nil == class_createInstance(Nil, 0)); + test(0 == class_getVersion(Nil)); + test(NO == class_isMetaClass(Nil)); + test(Nil == class_getSuperclass(Nil)); + + test(NULL == method_getName(NULL)); + test(NULL == method_copyArgumentType(NULL, 0)); + test(NULL == method_copyReturnType(NULL)); + method_exchangeImplementations(NULL, NULL); + test((IMP)NULL == method_setImplementation(NULL, (IMP)NULL)); + test((IMP)NULL == method_getImplementation(NULL)); + method_getArgumentType(NULL, 0, NULL, 0); + test(0 == method_getNumberOfArguments(NULL)); + test(NULL == method_getTypeEncoding(NULL)); + method_getReturnType(NULL, NULL, 0); + + test(NULL == ivar_getName(NULL)); + test(0 == ivar_getOffset(NULL)); + test(NULL == ivar_getTypeEncoding(NULL)); + + test(nil == objc_getProtocol(NULL)); + + test(stringsEqual("<null selector>", sel_getName((SEL)0))); + test((SEL)0 == sel_getUid(NULL)); + test(0 != sel_getUid("")); // the empty string is permitted as a selector + test(stringsEqual("", sel_getName(sel_getUid("")))); + test(YES == sel_isEqual((SEL)0, (SEL)0)); + + //test(NULL == property_getName(NULL)); + + printf("testInvalidArguments() ran\n"); +} + +void testAMethod(Method m) +{ + test(NULL != m); + test(stringsEqual("aMethod", sel_getName(method_getName(m)))); + + printf("testAMethod() ran\n"); +} + +void testGetMethod() +{ + testAMethod(class_getClassMethod([Bar class], @selector(aMethod))); + testAMethod(class_getClassMethod([Bar class], sel_getUid("aMethod"))); + + printf("testGetMethod() ran\n"); +} + +void testProtocols() +{ + test(protocol_isEqual(@protocol(NSCoding), objc_getProtocol("NSCoding"))); + + printf("testProtocols() ran\n"); +} + +void testMultiTypedSelector() +{ + test(sel_isEqual(@selector(manyTypes),sel_getUid("manyTypes"))); + test(@selector(manyTypes) == sel_getUid("manyTypes")); + + Method intMethod = class_getInstanceMethod([Foo class], @selector(manyTypes)); + Method idMethod = class_getInstanceMethod([Bar class], @selector(manyTypes)); + + test(method_getName(intMethod) == @selector(manyTypes)); + test(method_getName(idMethod) == @selector(manyTypes)); + + test(sel_isEqual(method_getName(intMethod), @selector(manyTypes))); + test(sel_isEqual(method_getName(idMethod), @selector(manyTypes))); + + char ret[10]; + method_getReturnType(intMethod, ret, 10); + test(stringsEqual(ret, "i")); + method_getReturnType(idMethod, ret, 10); + test(stringsEqual(ret, "@")); + + printf("testMultiTypedSelector() ran\n"); +} + +void testClassHierarchy() +{ + Class nsProxy = objc_getClass("NSProxy"); + Class nsObject = objc_getClass("NSObject"); + Class nsProxyMeta = object_getClass(nsProxy); + Class nsObjectMeta = object_getClass(nsObject); + + test(object_getClass(nsProxyMeta) == nsProxyMeta); + test(object_getClass(nsObjectMeta) == nsObjectMeta); + + test(Nil == class_getSuperclass(nsProxy)); + test(Nil == class_getSuperclass(nsObject)); + + test(nsObject == class_getSuperclass(nsObjectMeta)); + test(nsProxy == class_getSuperclass(nsProxyMeta)); + printf("testClassHierarchy() ran\n"); +} + +void testAllocateClass() +{ + Class newClass = objc_allocateClassPair(objc_lookUpClass("NSObject"), "UserAllocated", 0); + test(Nil != newClass); + // class_getSuperclass() will call objc_resolve_class(). + // Although we have not called objc_registerClassPair() yet, this works with + // the Apple runtime and GNUstep Base relies on this behavior in + // GSObjCMakeClass(). + test(objc_lookUpClass("NSObject") == class_getSuperclass(newClass)); + printf("testAllocateClass() ran\n"); +} + +void testSynchronized() +{ + Foo *foo = [Foo new]; + printf("Enter synchronized code\n"); + [foo synchronizedCode]; + [foo release]; + [Foo shared]; + printf("testSynchronized() ran\n"); +} + +void testExceptions() +{ + Foo *foo = [Foo new]; + test([foo basicThrowAndCatchException]); + [foo release]; + printf("testExceptions() ran\n"); + +} + +void testRegisterAlias() +{ + class_registerAlias_np([NSObject class], "AliasObject"); + test([NSObject class] == objc_getClass("AliasObject")); + printf("testRegisterAlias() ran\n"); +} + +@interface SlowInit1 ++ (void)doNothing; +@end +@interface SlowInit2 ++ (void)doNothing; +@end +@implementation SlowInit1 ++ (void)initialize +{ + sleep(1); + [SlowInit2 doNothing]; +} ++ (void)doNothing {} +@end +static int initCount; +@implementation SlowInit1 ++ (void)initialize +{ + sleep(1); + __sync_fetch_and_add(&initCount, 1); +} ++ (void)doNothing {} +@end + + + +int main (int argc, const char * argv[]) +{ + testInvalidArguments(); + testGetMethod(); + testProtocols(); + testMultiTypedSelector(); + testClassHierarchy(); + testAllocateClass(); + printf("Instance of NSObject: %p\n", class_createInstance([NSObject class], 0)); + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + testSynchronized(); + testExceptions(); + testRegisterAlias(); + [pool release]; + + return exitStatus; +} diff --git a/third_party/libobjc/Test/RuntimeTest.xcodeproj/project.pbxproj b/third_party/libobjc/Test/RuntimeTest.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000000000000000000000000000000..29fc1066624badb2dbb6e754c615fc74501016d1 --- /dev/null +++ b/third_party/libobjc/Test/RuntimeTest.xcodeproj/project.pbxproj @@ -0,0 +1,203 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 8DD76F9A0486AA7600D96B5E /* RuntimeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* RuntimeTest.m */; settings = {ATTRIBUTES = (); }; }; + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08FB7796FE84155DC02AAC07 /* RuntimeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeTest.m; sourceTree = "<group>"; }; + 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; }; + 8DD76FA10486AA7600D96B5E /* RuntimeTest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = RuntimeTest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* RuntimeTest */ = { + isa = PBXGroup; + children = ( + 08FB7795FE84155DC02AAC07 /* Source */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = RuntimeTest; + sourceTree = "<group>"; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 08FB7796FE84155DC02AAC07 /* RuntimeTest.m */, + ); + name = Source; + sourceTree = "<group>"; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 08FB779EFE84155DC02AAC07 /* Foundation.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = "<group>"; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76FA10486AA7600D96B5E /* RuntimeTest */, + ); + name = Products; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F960486AA7600D96B5E /* RuntimeTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "RuntimeTest" */; + buildPhases = ( + 8DD76F990486AA7600D96B5E /* Sources */, + 8DD76F9B0486AA7600D96B5E /* Frameworks */, + 8DD76F9E0486AA7600D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RuntimeTest; + productInstallPath = "$(HOME)/bin"; + productName = RuntimeTest; + productReference = 8DD76FA10486AA7600D96B5E /* RuntimeTest */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "RuntimeTest" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* RuntimeTest */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F960486AA7600D96B5E /* RuntimeTest */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F990486AA7600D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9A0486AA7600D96B5E /* RuntimeTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB927508733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = RuntimeTest; + }; + name = Debug; + }; + 1DEB927608733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_MODEL_TUNING = G5; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = RuntimeTest; + }; + name = Release; + }; + 1DEB927908733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.6; + }; + name = Debug; + }; + 1DEB927A08733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = macosx10.6; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "RuntimeTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927508733DD40010E9CD /* Debug */, + 1DEB927608733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "RuntimeTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927908733DD40010E9CD /* Debug */, + 1DEB927A08733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/third_party/libobjc/Test/objc_msgSend.m b/third_party/libobjc/Test/objc_msgSend.m new file mode 100644 index 0000000000000000000000000000000000000000..ff179cda9c3471a413d726c03e2330a4f6900463 --- /dev/null +++ b/third_party/libobjc/Test/objc_msgSend.m @@ -0,0 +1,143 @@ +#include <time.h> +#include <stdio.h> +#include <objc/runtime.h> +#include <assert.h> +#include <string.h> +#include <class.h> +#include <stdarg.h> + +//#define assert(x) if (!(x)) { printf("Failed %d\n", __LINE__); } + +id objc_msgSend(id, SEL, ...); + +typedef struct { int a,b,c,d,e; } s; +s objc_msgSend_stret(id, SEL, ...); +@interface Fake +- (int)izero; +- (float)fzero; +- (double)dzero; +- (long double)ldzero; +@end + +Class TestCls; +@interface Test { id isa; }@end +@implementation Test +- foo +{ + assert((id)1 == self); + assert(strcmp("foo", sel_getName(_cmd)) == 0); + return (id)0x42; +} ++ foo +{ + assert(TestCls == self); + assert(strcmp("foo", sel_getName(_cmd)) == 0); + return (id)0x42; +} ++ (s)sret +{ + assert(TestCls == self); + assert(strcmp("sret", sel_getName(_cmd)) == 0); + s st = {1,2,3,4,5}; + return st; +} +- (s)sret +{ + assert((id)3 == self); + assert(strcmp("sret", sel_getName(_cmd)) == 0); + s st = {1,2,3,4,5}; + return st; +} ++ (void)printf: (const char*)str, ... +{ + va_list ap; + char *s; + + va_start(ap, str); + + vasprintf(&s, str, ap); + va_end(ap); + //fprintf(stderr, "String: '%s'\n", s); + assert(strcmp(s, "Format string 42 42.000000\n") ==0); +} ++ (void)initialize +{ + [self printf: "Format %s %d %f%c", "string", 42, 42.0, '\n']; + @throw self; +} ++ nothing { return 0; } +@end +int main(void) +{ + TestCls = objc_getClass("Test"); + int exceptionThrown = 0; + @try { + objc_msgSend(TestCls, @selector(foo)); + } @catch (id e) + { + assert((TestCls == e) && "Exceptions propagate out of +initialize"); + exceptionThrown = 1; + } + assert(exceptionThrown && "An exception was thrown"); + assert((id)0x42 == objc_msgSend(TestCls, @selector(foo))); + objc_msgSend(TestCls, @selector(nothing)); + objc_msgSend(TestCls, @selector(missing)); + assert(0 == objc_msgSend(0, @selector(nothing))); + id a = objc_msgSend(objc_getClass("Test"), @selector(foo)); + assert((id)0x42 == a); + a = objc_msgSend(TestCls, @selector(foo)); + assert((id)0x42 == a); + assert(objc_registerSmallObjectClass_np(objc_getClass("Test"), 1)); + a = objc_msgSend((id)01, @selector(foo)); + assert((id)0x42 == a); + s ret = objc_msgSend_stret(TestCls, @selector(sret)); + assert(ret.a == 1); + assert(ret.b == 2); + assert(ret.c == 3); + assert(ret.d == 4); + assert(ret.e == 5); + if (sizeof(id) == 8) + { + assert(objc_registerSmallObjectClass_np(objc_getClass("Test"), 3)); + ret = objc_msgSend_stret((id)3, @selector(sret)); + assert(ret.a == 1); + assert(ret.b == 2); + assert(ret.c == 3); + assert(ret.d == 4); + assert(ret.e == 5); + } + Fake *f = nil; + assert(0 == [f izero]); + assert(0 == [f dzero]); + assert(0 == [f ldzero]); + assert(0 == [f fzero]); +#ifdef BENCHMARK + clock_t c1, c2; + c1 = clock(); + for (int i=0 ; i<100000000 ; i++) + { + [TestCls nothing]; + } + c2 = clock(); + printf("Traditional message send took %f seconds. \n", + ((double)c2 - (double)c1) / (double)CLOCKS_PER_SEC); + c1 = clock(); + for (int i=0 ; i<100000000 ; i++) + { + objc_msgSend(TestCls, @selector(nothing)); + } + c2 = clock(); + printf("objc_msgSend() message send took %f seconds. \n", + ((double)c2 - (double)c1) / (double)CLOCKS_PER_SEC); + IMP nothing = objc_msg_lookup(TestCls, @selector(nothing)); + c1 = clock(); + for (int i=0 ; i<100000000 ; i++) + { + nothing(TestCls, @selector(nothing)); + } + c2 = clock(); + printf("Direct IMP call took %f seconds. \n", + ((double)c2 - (double)c1) / (double)CLOCKS_PER_SEC); +#endif + return 0; +} diff --git a/third_party/libobjc/abi_version.c b/third_party/libobjc/abi_version.c new file mode 100644 index 0000000000000000000000000000000000000000..a75a3f5cc2816cb053ce6c5985e937804e63b4bd --- /dev/null +++ b/third_party/libobjc/abi_version.c @@ -0,0 +1,143 @@ +#include "visibility.h" +#include "objc/runtime.h" +#include "module.h" +#include "gc_ops.h" +#include <assert.h> +#include <stdio.h> +#include <string.h> + +/** + * The smallest ABI version number of loaded modules. + */ +static unsigned long min_loaded_version; +/** + * The largest ABI version number of loaded modules. + */ +static unsigned long max_loaded_version; + +/** + * Structure defining the compatibility between Objective-C ABI versions. + */ +struct objc_abi_version +{ + /** Version of this ABI. */ + unsigned long version; + /** Lowest ABI version that this is compatible with. */ + unsigned long min_compatible_version; + /** Highest ABI version compatible with this. */ + unsigned long max_compatible_version; + /** Size of the module structure for this ABI version. */ + unsigned long module_size; +}; + +enum +{ + gcc_abi = 8, + gnustep_abi = 9, + gc_abi = 10 +}; + +/** + * List of supported ABIs. + */ +static struct objc_abi_version known_abis[] = +{ + /* GCC ABI. */ + {gcc_abi, gcc_abi, gnustep_abi, sizeof(struct objc_module_abi_8)}, + /* Non-fragile ABI. */ + {gnustep_abi, gcc_abi, gc_abi, sizeof(struct objc_module_abi_8)}, + /* GC ABI. Adds a field describing the GC mode. */ + {gc_abi, gcc_abi, gc_abi, sizeof(struct objc_module_abi_10)} +}; + +static int known_abi_count = + (sizeof(known_abis) / sizeof(struct objc_abi_version)); + +#define FAIL_IF(x, msg) do {\ + if (x)\ + {\ + fprintf(stderr, "Objective-C ABI Error: %s while loading %s\n", msg, module->name);\ + return NO;\ + }\ +} while(0) + +PRIVATE enum objc_gc_mode current_gc_mode = GC_Optional; + +static BOOL endsWith(const char *string, const char *suffix) +{ + if (NULL == string) { return NO; } + char *interior = strstr(string, suffix); + return (interior && (strlen(suffix) == strlen(interior))); +} + +PRIVATE BOOL objc_check_abi_version(struct objc_module_abi_8 *module) +{ + static int runtime_modules = 5; + // As a quick and ugly hack, skip these three tests for the .m files in the + // runtime. They should (in theory, at least) be aware of the GC mode and + // behave accordingly. + if (runtime_modules > 0) + { + if (endsWith(module->name, "properties.m") || + endsWith(module->name, "associate.m") || + endsWith(module->name, "arc.m") || + endsWith(module->name, "blocks_runtime.m") || + endsWith(module->name, "Protocol2.m")) + { + runtime_modules--; + return YES; + } + } + unsigned long version = module->version; + unsigned long module_size = module->size; + enum objc_gc_mode gc_mode = (version < gc_abi) ? GC_None + : ((struct objc_module_abi_10*)module)->gc_mode; + struct objc_abi_version *v = NULL; + for (int i=0 ; i<known_abi_count ; i++) + { + if (known_abis[i].version == version) + { + v = &known_abis[i]; + break; + } + } + FAIL_IF(NULL == v, "Unknown ABI version"); + FAIL_IF((v->module_size != module_size), "Incorrect module size"); + // Only check for ABI compatibility if + if (min_loaded_version > 0) + { + FAIL_IF((v->min_compatible_version > min_loaded_version), + "Loading modules from incompatible ABIs"); + FAIL_IF((v->max_compatible_version < max_loaded_version), + "Loading modules from incompatible ABIs"); + if (min_loaded_version > version) + { + min_loaded_version = version; + } + if (max_loaded_version < version) + { + max_loaded_version = version; + } + } + else + { + min_loaded_version = version; + max_loaded_version = version; + } + + // If we're currently in GC-optional mode, then fall to one side or the + // other if this module requires / doesn't support GC + if (current_gc_mode == GC_Optional) + { + current_gc_mode = gc_mode; + if (gc_mode == GC_Required) + { + enableGC(NO); + } + } + // We can't mix GC_None and GC_Required code, but we can mix any other + // combination + FAIL_IF((gc_mode == GC_Required) && (gc_mode != current_gc_mode), + "Attempting to mix GC and non-GC code!"); + return YES; +} diff --git a/third_party/libobjc/alias.h b/third_party/libobjc/alias.h new file mode 100644 index 0000000000000000000000000000000000000000..edf5ef30addef130b0a73aa6cea877d9dbf7c2e8 --- /dev/null +++ b/third_party/libobjc/alias.h @@ -0,0 +1,27 @@ +/** Declaration of a helper function for getting class references from aliases. + Copyright (c) 2011 Free Software Foundation, Inc. + + Written by: Niels Grewe <niels.grewe@halbordnung.de> + Created: March 2011 + + 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. +*/ +#include "objc/runtime.h" + +Class alias_getClass(const char *alias_name); diff --git a/third_party/libobjc/alias_table.c b/third_party/libobjc/alias_table.c new file mode 100644 index 0000000000000000000000000000000000000000..9fda1cd6df4cccd118f5402125a266c766ff25f2 --- /dev/null +++ b/third_party/libobjc/alias_table.c @@ -0,0 +1,126 @@ +/** A hash table for mapping compatibility aliases to classes. + Copyright (c) 2011 Free Software Foundation, Inc. + + Written by: Niels Grewe <niels.grewe@halbordnung.de> + Created: March 2011 + + 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. +*/ + +#include "visibility.h" +#include "objc/runtime.h" +#include "class.h" +#include "lock.h" +#include "string_hash.h" + +#include <stdlib.h> + +struct objc_alias +{ + const char* name; + Class class; +}; + +typedef struct objc_alias Alias; + +static int alias_compare(const char *name, const Alias alias) +{ + return string_compare(name, alias.name); +} + +static int alias_hash(const Alias alias) +{ + return string_hash(alias.name); +} +static int alias_is_null(const Alias alias) +{ + return alias.name == NULL; +} +static Alias NullAlias; +#define MAP_TABLE_NAME alias_table_internal +#define MAP_TABLE_COMPARE_FUNCTION alias_compare +#define MAP_TABLE_HASH_KEY string_hash +#define MAP_TABLE_HASH_VALUE alias_hash +#define MAP_TABLE_VALUE_TYPE struct objc_alias +#define MAP_TABLE_VALUE_NULL alias_is_null +#define MAP_TABLE_VALUE_PLACEHOLDER NullAlias + +#include "hash_table.h" + +static alias_table_internal_table *alias_table; + +PRIVATE void init_alias_table(void) +{ + alias_table_internal_initialize(&alias_table, 128); +} + + +static Alias alias_table_get_safe(const char *alias_name) +{ + return alias_table_internal_table_get(alias_table, alias_name); +} + + +Class alias_getClass(const char *alias_name) +{ + if (NULL == alias_name) + { + return NULL; + } + + Alias alias = alias_table_get_safe(alias_name); + + if (NULL == alias.name) + { + return NULL; + } + + return alias.class; +} + +PRIVATE void alias_table_insert(Alias alias) +{ + alias_table_internal_insert(alias_table, alias); +} + +BOOL class_registerAlias_np(Class class, const char *alias) +{ + if ((NULL == alias) || (NULL == class)) + { + return 0; + } + + /* + * If there already exists a matching alias, determine whether we the existing + * alias is the correct one. Please note that objc_getClass() goes through the + * alias lookup and will create the alias table if necessary. + */ + Class existingClass = (Class)objc_getClass(alias); + if (NULL != existingClass) + { + /* + * Return YES if the alias has already been registered for this very + * class, and NO if the alias is already used for another class. + */ + return (class == existingClass); + } + Alias newAlias = { strdup(alias), class }; + alias_table_insert(newAlias); + return 1; +} diff --git a/third_party/libobjc/arc.m b/third_party/libobjc/arc.m new file mode 100644 index 0000000000000000000000000000000000000000..b8c6d7cfbe5801a380d939c6232287952de1930b --- /dev/null +++ b/third_party/libobjc/arc.m @@ -0,0 +1,690 @@ +#include <stdlib.h> +#include <assert.h> +#import "stdio.h" +#import "objc/runtime.h" +#import "objc/blocks_runtime.h" +#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" + +#ifndef NO_PTHREADS +#include <pthread.h> +pthread_key_t ARCThreadKey; +#endif + +extern void _NSConcreteMallocBlock; +extern void _NSConcreteGlobalBlock; + +@interface NSAutoreleasePool ++ (Class)class; ++ (id)new; +- (void)release; +@end + +#define POOL_SIZE (4096 / sizeof(void*) - (2 * sizeof(void*))) +/** + * Structure used for ARC-managed autorelease pools. This structure should be + * exactly one page in size, so that it can be quickly allocated. This does + * not correspond directly to an autorelease pool. The 'pool' returned by + * objc_autoreleasePoolPush() may be an interior pointer to one of these + * structures. + */ +struct arc_autorelease_pool +{ + /** + * Pointer to the previous autorelease pool structure in the chain. Set + * when pushing a new structure on the stack, popped during cleanup. + */ + struct arc_autorelease_pool *previous; + /** + * The current insert point. + */ + id *insert; + /** + * The remainder of the page, an array of object pointers. + */ + id pool[POOL_SIZE]; +}; + +struct arc_tls +{ + struct arc_autorelease_pool *pool; + id returnRetained; +}; + +static inline struct arc_tls* getARCThreadData(void) +{ +#ifdef NO_PTHREADS + return NULL; +#else + struct arc_tls *tls = pthread_getspecific(ARCThreadKey); + if (NULL == tls) + { + tls = calloc(sizeof(struct arc_tls), 1); + pthread_setspecific(ARCThreadKey, tls); + } + return tls; +#endif +} +int count = 0; +int poolCount = 0; +static inline void release(id obj); + +/** + * Empties objects from the autorelease pool, stating at the head of the list + * specified by pool and continuing until it reaches the stop point. If the stop point is NULL then + */ +static void emptyPool(struct arc_tls *tls, id *stop) +{ + struct arc_autorelease_pool *stopPool = NULL; + if (NULL != stop) + { + stopPool = tls->pool; + while (1) + { + // Invalid stop location + if (NULL == stopPool) + { + return; + } + // NULL is the placeholder for the top-level pool + if (NULL == stop && stopPool->previous == NULL) + { + break; + } + // Stop location was found in this pool + if ((stop >= stopPool->pool) && (stop < &stopPool->pool[POOL_SIZE])) + { + break; + } + stopPool = stopPool->previous; + } + } + while (tls->pool != stopPool) + { + while (tls->pool->insert > tls->pool->pool) + { + tls->pool->insert--; + // This may autorelease some other objects, so we have to work in + // the case where the autorelease pool is extended during a -release. + release(*tls->pool->insert); + count--; + } + void *old = tls->pool; + tls->pool = tls->pool->previous; + free(old); + } + if (NULL != tls->pool) + { + while ((stop == NULL || (tls->pool->insert > stop)) && + (tls->pool->insert > tls->pool->pool)) + { + tls->pool->insert--; + count--; + release(*tls->pool->insert); + } + } + //fprintf(stderr, "New insert: %p. Stop: %p\n", tls->pool->insert, stop); +} + +static void cleanupPools(struct arc_tls* tls) +{ + struct arc_autorelease_pool *pool = tls->pool; + if (tls->returnRetained) + { + release(tls->returnRetained); + tls->returnRetained = nil; + } + while(NULL != pool) + { + emptyPool(tls, NULL); + assert(NULL == tls->pool); + } + if (tls->returnRetained) + { + cleanupPools(tls); + } + free(tls); +} + + +static Class AutoreleasePool; +static IMP NewAutoreleasePool; +static IMP DeleteAutoreleasePool; +static IMP AutoreleaseAdd; + +extern BOOL FastARCRetain; +extern BOOL FastARCRelease; +extern BOOL FastARCAutorelease; + +static BOOL useARCAutoreleasePool; + +static inline id retain(id obj) +{ + if (isSmallObject(obj)) { return obj; } + Class cls = obj->isa; + if ((Class)&_NSConcreteMallocBlock == cls) + { + return Block_copy(obj); + } + if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + intptr_t *refCount = ((intptr_t*)obj) - 1; + __sync_add_and_fetch(refCount, 1); + return obj; + } + return [obj retain]; +} + +static inline void release(id obj) +{ + if (isSmallObject(obj)) { return; } + Class cls = obj->isa; + if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + intptr_t *refCount = ((intptr_t*)obj) - 1; + if (__sync_sub_and_fetch(refCount, 1) < 0) + { + objc_delete_weak_refs(obj); + [obj dealloc]; + } + return; + } + [obj release]; +} + +static inline void initAutorelease(void) +{ + if (Nil == AutoreleasePool) + { + AutoreleasePool = objc_getRequiredClass("NSAutoreleasePool"); + if (Nil == AutoreleasePool) + { + useARCAutoreleasePool = YES; + } + else + { + [AutoreleasePool class]; + useARCAutoreleasePool = class_respondsToSelector(AutoreleasePool, + SELECTOR(_ARCCompatibleAutoreleasePool)); + NewAutoreleasePool = class_getMethodImplementation(object_getClass(AutoreleasePool), + SELECTOR(new)); + DeleteAutoreleasePool = class_getMethodImplementation(AutoreleasePool, + SELECTOR(release)); + AutoreleaseAdd = class_getMethodImplementation(object_getClass(AutoreleasePool), + SELECTOR(addObject:)); + } + } +} + +static inline id autorelease(id obj) +{ + //fprintf(stderr, "Autoreleasing %p\n", obj); + if (useARCAutoreleasePool) + { + struct arc_tls *tls = getARCThreadData(); + if (NULL != tls) + { + struct arc_autorelease_pool *pool = tls->pool; + if (NULL == pool || (pool->insert >= &pool->pool[POOL_SIZE])) + { + pool = calloc(sizeof(struct arc_autorelease_pool), 1); + pool->previous = tls->pool; + pool->insert = pool->pool; + tls->pool = pool; + } + count++; + *pool->insert = obj; + pool->insert++; + return obj; + } + } + if (objc_test_class_flag(classForObject(obj), objc_class_flag_fast_arc)) + { + initAutorelease(); + if (0 != AutoreleaseAdd) + { + AutoreleaseAdd(AutoreleasePool, SELECTOR(addObject:), obj); + } + return obj; + } + return [obj autorelease]; +} + +unsigned long objc_arc_autorelease_count_np(void) +{ + struct arc_tls* tls = getARCThreadData(); + unsigned long count = 0; + if (!tls) { return 0; } + + for (struct arc_autorelease_pool *pool=tls->pool ; + NULL != pool ; + pool = pool->previous) + { + count += (((intptr_t)pool->insert) - ((intptr_t)pool->pool)) / sizeof(id); + } + return count; +} +unsigned long objc_arc_autorelease_count_for_object_np(id obj) +{ + struct arc_tls* tls = getARCThreadData(); + unsigned long count = 0; + if (!tls) { return 0; } + + for (struct arc_autorelease_pool *pool=tls->pool ; + NULL != pool ; + pool = pool->previous) + { + for (id* o = pool->insert-1 ; o >= pool->pool ; o--) + { + if (*o == obj) + { + count++; + } + } + } + return count; +} + + +void *objc_autoreleasePoolPush(void) +{ + initAutorelease(); + struct arc_tls* tls = getARCThreadData(); + // If there is an object in the return-retained slot, then we need to + // promote it to the real autorelease pool BEFORE pushing the new + // autorelease pool. If we don't, then it may be prematurely autoreleased. + if ((NULL != tls) && (nil != tls->returnRetained)) + { + autorelease(tls->returnRetained); + tls->returnRetained = nil; + } + if (useARCAutoreleasePool) + { + if (NULL != tls) + { + struct arc_autorelease_pool *pool = tls->pool; + if (NULL == pool || (pool->insert >= &pool->pool[POOL_SIZE])) + { + pool = calloc(sizeof(struct arc_autorelease_pool), 1); + pool->previous = tls->pool; + pool->insert = pool->pool; + tls->pool = pool; + } + // If there is no autorelease pool allocated for this thread, then + // we lazily allocate one the first time something is autoreleased. + return (NULL != tls->pool) ? tls->pool->insert : NULL; + } + } + initAutorelease(); + if (0 == NewAutoreleasePool) { return NULL; } + return NewAutoreleasePool(AutoreleasePool, SELECTOR(new)); +} +void objc_autoreleasePoolPop(void *pool) +{ + if (useARCAutoreleasePool) + { + struct arc_tls* tls = getARCThreadData(); + if (NULL != tls) + { + if (NULL != tls->pool) + { + emptyPool(tls, pool); + } + return; + } + } + DeleteAutoreleasePool(pool, SELECTOR(release)); + struct arc_tls* tls = getARCThreadData(); + if (tls && tls->returnRetained) + { + release(tls->returnRetained); + tls->returnRetained = nil; + } +} + +id objc_autorelease(id obj) +{ + if (nil != obj) + { + obj = autorelease(obj); + } + return obj; +} + +id objc_autoreleaseReturnValue(id obj) +{ + if (!useARCAutoreleasePool) + { + struct arc_tls* tls = getARCThreadData(); + if (NULL != tls) + { + objc_autorelease(tls->returnRetained); + tls->returnRetained = obj; + return obj; + } + } + return objc_autorelease(obj); +} + +id objc_retainAutoreleasedReturnValue(id obj) +{ + // If the previous object was released with objc_autoreleaseReturnValue() + // just before return, then it will not have actually been autoreleased. + // Instead, it will have been stored in TLS. We just remove it from TLS + // and undo the fake autorelease. + // + // If the object was not returned with objc_autoreleaseReturnValue() then + // we actually autorelease the fake object. and then retain the argument. + // In tis case, this is equivalent to objc_retain(). + struct arc_tls* tls = getARCThreadData(); + if (NULL != tls) + { + // If we're using our own autorelease pool, just pop the object from the top + if (useARCAutoreleasePool) + { + if ((NULL != tls->pool) && + (*(tls->pool->insert-1) == obj)) + { + tls->pool->insert--; + return obj; + } + } + else if (obj == tls->returnRetained) + { + tls->returnRetained = NULL; + return obj; + } + } + return objc_retain(obj); +} + +id objc_retain(id obj) +{ + if (nil == obj) { return nil; } + return retain(obj); +} + +id objc_retainAutorelease(id obj) +{ + return objc_autorelease(objc_retain(obj)); +} + +id objc_retainAutoreleaseReturnValue(id obj) +{ + if (nil == obj) { return obj; } + return objc_autoreleaseReturnValue(retain(obj)); +} + + +id objc_retainBlock(id b) +{ + return _Block_copy(b); +} + +void objc_release(id obj) +{ + if (nil == obj) { return; } + release(obj); +} + +id objc_storeStrong(id *addr, id value) +{ + value = objc_retain(value); + id oldValue = *addr; + *addr = value; + objc_release(oldValue); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Weak references +//////////////////////////////////////////////////////////////////////////////// + +typedef struct objc_weak_ref +{ + id obj; + id *ref[4]; + struct objc_weak_ref *next; +} WeakRef; + + +static int weak_ref_compare(const id obj, const WeakRef weak_ref) +{ + return obj == weak_ref.obj; +} + +static uint32_t ptr_hash(const void *ptr) +{ + // Bit-rotate right 4, since the lowest few bits in an object pointer will + // always be 0, which is not so useful for a hash value + return ((uintptr_t)ptr >> 4) | ((uintptr_t)ptr << ((sizeof(id) * 8) - 4)); +} +static int weak_ref_hash(const WeakRef weak_ref) +{ + return ptr_hash(weak_ref.obj); +} +static int weak_ref_is_null(const WeakRef weak_ref) +{ + return weak_ref.obj == NULL; +} +const static WeakRef NullWeakRef; +#define MAP_TABLE_NAME weak_ref +#define MAP_TABLE_COMPARE_FUNCTION weak_ref_compare +#define MAP_TABLE_HASH_KEY ptr_hash +#define MAP_TABLE_HASH_VALUE weak_ref_hash +#define MAP_TABLE_HASH_VALUE weak_ref_hash +#define MAP_TABLE_VALUE_TYPE struct objc_weak_ref +#define MAP_TABLE_VALUE_NULL weak_ref_is_null +#define MAP_TABLE_VALUE_PLACEHOLDER NullWeakRef +#define MAP_TABLE_ACCESS_BY_REFERENCE 1 +#define MAP_TABLE_SINGLE_THREAD 1 +#define MAP_TABLE_NO_LOCK 1 + +#include "hash_table.h" + +static weak_ref_table *weakRefs; +mutex_t weakRefLock; + +PRIVATE void init_arc(void) +{ + weak_ref_initialize(&weakRefs, 128); + INIT_LOCK(weakRefLock); +#ifndef NO_PTHREADS + pthread_key_create(&ARCThreadKey, (void(*)(void*))cleanupPools); +#endif +} + +void* block_load_weak(void *block); + +id objc_storeWeak(id *addr, id obj) +{ + id old = *addr; + LOCK_FOR_SCOPE(&weakRefLock); + if (nil != old) + { + WeakRef *oldRef = weak_ref_table_get(weakRefs, old); + while (NULL != oldRef) + { + for (int i=0 ; i<4 ; i++) + { + if (oldRef->ref[i] == addr) + { + oldRef->ref[i] = 0; + oldRef = 0; + break; + } + } + oldRef = oldRef->next; + } + } + if (nil == obj) + { + *addr = obj; + return nil; + } + Class cls = classForObject(obj); + if (&_NSConcreteGlobalBlock == cls) + { + // If this is a global block, it's never deallocated, so secretly make + // this a strong reference + // TODO: We probably also want to do the same for constant strings and + // classes. + *addr = obj; + return obj; + } + if (&_NSConcreteMallocBlock == cls) + { + obj = block_load_weak(obj); + } + else if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + if ((*(((intptr_t*)obj) - 1)) < 0) + { + return nil; + } + } + else + { + obj = _objc_weak_load(obj); + } + if (nil != obj) + { + WeakRef *ref = weak_ref_table_get(weakRefs, obj); + while (NULL != ref) + { + for (int i=0 ; i<4 ; i++) + { + if (0 == ref->ref[i]) + { + ref->ref[i] = addr; + *addr = obj; + return obj; + } + } + if (ref->next == NULL) + { + break; + } + ref = ref->next; + } + if (NULL != ref) + { + ref->next = calloc(sizeof(WeakRef), 1); + ref->next->ref[0] = addr; + } + else + { + WeakRef newRef = {0}; + newRef.obj = obj; + newRef.ref[0] = addr; + weak_ref_insert(weakRefs, newRef); + } + } + *addr = obj; + return obj; +} + +static void zeroRefs(WeakRef *ref, BOOL shouldFree) +{ + if (NULL != ref->next) + { + zeroRefs(ref->next, YES); + } + for (int i=0 ; i<4 ; i++) + { + if (0 != ref->ref[i]) + { + *ref->ref[i] = 0; + } + } + if (shouldFree) + { + free(ref); + } + else + { + memset(ref, 0, sizeof(WeakRef)); + } +} + +void objc_delete_weak_refs(id obj) +{ + LOCK_FOR_SCOPE(&weakRefLock); + WeakRef *oldRef = weak_ref_table_get(weakRefs, obj); + if (0 != oldRef) + { + zeroRefs(oldRef, NO); + } +} + +id objc_loadWeakRetained(id* addr) +{ + LOCK_FOR_SCOPE(&weakRefLock); + id obj = *addr; + if (nil == obj) { return nil; } + Class cls = classForObject(obj); + if (&_NSConcreteMallocBlock == cls) + { + obj = block_load_weak(obj); + } + else if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + if ((*(((intptr_t*)obj) - 1)) < 0) + { + return nil; + } + } + else + { + obj = _objc_weak_load(obj); + } + return objc_retain(obj); +} + +id objc_loadWeak(id* object) +{ + return objc_autorelease(objc_loadWeakRetained(object)); +} + +void objc_copyWeak(id *dest, id *src) +{ + objc_release(objc_initWeak(dest, objc_loadWeakRetained(src))); +} + +void objc_moveWeak(id *dest, id *src) +{ + // Don't retain or release. While the weak ref lock is held, we know that + // the object can't be deallocated, so we just move the value and update + // the weak reference table entry to indicate the new address. + LOCK_FOR_SCOPE(&weakRefLock); + *dest = *src; + *src = nil; + WeakRef *oldRef = weak_ref_table_get(weakRefs, *dest); + while (NULL != oldRef) + { + for (int i=0 ; i<4 ; i++) + { + if (oldRef->ref[i] == src) + { + oldRef->ref[i] = dest; + return; + } + } + } +} + +void objc_destroyWeak(id* obj) +{ + objc_storeWeak(obj, nil); +} + +id objc_initWeak(id *object, id value) +{ + *object = nil; + return objc_storeWeak(object, value); +} diff --git a/third_party/libobjc/associate.m b/third_party/libobjc/associate.m new file mode 100644 index 0000000000000000000000000000000000000000..2289dcaee2b518e095832ac29fdf4834232d8686 --- /dev/null +++ b/third_party/libobjc/associate.m @@ -0,0 +1,435 @@ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include "nsobject.h" +#include "spinlock.h" +#include "class.h" +#include "dtable.h" +#include "selector.h" +#include "lock.h" +#include "gc_ops.h" + +/** + * A single associative reference. Contains the key, value, and association + * policy. + */ +struct reference +{ + /** + * The key used for identifying this object. Opaque pointer, should be set + * to 0 when this slot is unused. + */ + void *key; + /** + * The associated object. Note, if the policy is assign then this may be + * some other type of pointer... + */ + void *object; + /** + * Association policy. + */ + uintptr_t policy; +}; + +#define REFERENCE_LIST_SIZE 10 + +/** + * Linked list of references associated with an object. We assume that there + * won't be very many, so we don't bother with a proper hash table, and just + * iterate over a list. + */ +struct reference_list +{ + /** + * Next group of references. This is only ever used if we have more than + * 10 references associated with an object, which seems highly unlikely. + */ + struct reference_list *next; + /** + * Mutex. Only set for the first reference list in a chain. Used for + * @syncronize(). + */ + mutex_t lock; + /** + * Garbage collection type. This stores the location of all of the + * instance variables in the object that may contain pointers. + */ + void *gc_type; + /** + * Array of references. + */ + struct reference list[REFERENCE_LIST_SIZE]; +}; +enum +{ + OBJC_ASSOCIATION_ATOMIC = 0x300, +}; + +static BOOL isAtomic(uintptr_t policy) +{ + return (policy & OBJC_ASSOCIATION_ATOMIC) == OBJC_ASSOCIATION_ATOMIC; +} + +static struct reference* findReference(struct reference_list *list, void *key) +{ + if (NULL == list) { return NULL; } + + for (int i=0 ; i<REFERENCE_LIST_SIZE ; i++) + { + if (list->list[i].key == key) + { + return &list->list[i]; + } + } + return NULL; +} +static void cleanupReferenceList(struct reference_list *list) +{ + if (NULL == list) { return; } + + cleanupReferenceList(list->next); + + for (int i=0 ; i<REFERENCE_LIST_SIZE ; i++) + { + struct reference *r = &list->list[i]; + if (0 != r->key) + { + r->key = 0; + if (OBJC_ASSOCIATION_ASSIGN != r->policy) + { + // Full barrier - ensure that we've zero'd the key before doing + // this! + __sync_synchronize(); + objc_release(r->object); + } + r->object = 0; + r->policy = 0; + } + } +} + +static void freeReferenceList(struct reference_list *l) +{ + if (NULL == l) { return; } + freeReferenceList(l->next); + gc->free(l); +} + +static void setReference(struct reference_list *list, + void *key, + void *obj, + uintptr_t policy) +{ + switch (policy) + { + // Ignore any unknown association policies + default: return; + case OBJC_ASSOCIATION_COPY_NONATOMIC: + case OBJC_ASSOCIATION_COPY: + obj = [(id)obj copy]; + break; + case OBJC_ASSOCIATION_RETAIN_NONATOMIC: + case OBJC_ASSOCIATION_RETAIN: + obj = objc_retain(obj); + case OBJC_ASSOCIATION_ASSIGN: + break; + } + // While inserting into the list, we need to lock it temporarily. + volatile int *lock = lock_for_pointer(list); + lock_spinlock(lock); + struct reference *r = findReference(list, key); + // If there's an existing reference, then we can update it, otherwise we + // have to install a new one + if (NULL == r) + { + // Search for an unused slot + r = findReference(list, 0); + if (NULL == r) + { + struct reference_list *l = list; + + while (NULL != l->next) { l = l->next; } + + l->next = gc->malloc(sizeof(struct reference_list)); + r = &l->next->list[0]; + } + r->key = key; + } + unlock_spinlock(lock); + // Now we only need to lock if the old or new property is atomic + BOOL needLock = isAtomic(r->policy) || isAtomic(policy); + if (needLock) + { + lock = lock_for_pointer(r); + lock_spinlock(lock); + } + r->policy = policy; + id old = r->object; + r->object = obj; + if (OBJC_ASSOCIATION_ASSIGN != r->policy) + { + objc_release(old); + } + if (needLock) + { + unlock_spinlock(lock); + } +} + +static void deallocHiddenClass(id obj, SEL _cmd); + +static inline Class findHiddenClass(id obj) +{ + Class cls = obj->isa; + while (Nil != cls && + !objc_test_class_flag(cls, objc_class_flag_assoc_class)) + { + cls = class_getSuperclass(cls); + } + return cls; +} + +static Class allocateHiddenClass(Class superclass) +{ + Class newClass = + calloc(1, sizeof(struct objc_class) + sizeof(struct reference_list)); + + if (Nil == newClass) { return Nil; } + + // Set up the new class + newClass->isa = superclass->isa; + newClass->name = superclass->name; + // Uncomment this for debugging: it makes it easier to track which hidden + // class is which + // static int count; + //asprintf(&newClass->name, "%s%d", superclass->name, count++); + newClass->info = objc_class_flag_resolved | + objc_class_flag_class | objc_class_flag_user_created | + objc_class_flag_new_abi | objc_class_flag_hidden_class | + objc_class_flag_assoc_class; + newClass->super_class = superclass; + newClass->dtable = uninstalled_dtable; + newClass->instance_size = superclass->instance_size; + + newClass->sibling_class = superclass->subclass_list; + superclass->subclass_list = newClass; + + return newClass; +} + +static inline Class initHiddenClassForObject(id obj) +{ + Class hiddenClass = allocateHiddenClass(obj->isa); + assert(!class_isMetaClass(obj->isa)); + static SEL cxx_destruct; + if (NULL == cxx_destruct) + { + cxx_destruct = sel_registerName(".cxx_destruct"); + } + const char *types = sizeof(void*) == 4 ? "v8@0:4" : "v16@0:8"; + class_addMethod(hiddenClass, cxx_destruct, + (IMP)deallocHiddenClass, types); + obj->isa = hiddenClass; + return hiddenClass; +} + +static void deallocHiddenClass(id obj, SEL _cmd) +{ + Class hiddenClass = findHiddenClass(obj); + // After calling [super dealloc], the object will no longer exist. + // Free the hidden + struct reference_list *list = object_getIndexedIvars(hiddenClass); + DESTROY_LOCK(&list->lock); + cleanupReferenceList(list); + freeReferenceList(list->next); + free_dtable(hiddenClass->dtable); + // Free the class + free(hiddenClass); +} + +static struct reference_list* referenceListForObject(id object, BOOL create) +{ + if (class_isMetaClass(object->isa)) + { + Class cls = (Class)object; + if ((NULL == cls->extra_data) && create) + { + volatile int *lock = lock_for_pointer(cls); + struct reference_list *list = gc->malloc(sizeof(struct reference_list)); + lock_spinlock(lock); + if (NULL == cls->extra_data) + { + INIT_LOCK(list->lock); + cls->extra_data = list; + unlock_spinlock(lock); + } + else + { + unlock_spinlock(lock); + gc->free(list); + } + } + return cls->extra_data; + } + Class hiddenClass = findHiddenClass(object); + if ((NULL == hiddenClass) && create) + { + volatile int *lock = lock_for_pointer(object); + lock_spinlock(lock); + hiddenClass = findHiddenClass(object); + if (NULL == hiddenClass) + { + hiddenClass = initHiddenClassForObject(object); + struct reference_list *list = object_getIndexedIvars(hiddenClass); + INIT_LOCK(list->lock); + } + unlock_spinlock(lock); + } + return hiddenClass ? object_getIndexedIvars(hiddenClass) : NULL; +} + +void objc_setAssociatedObject(id object, + void *key, + id value, + objc_AssociationPolicy policy) +{ + if (isSmallObject(object)) { return; } + struct reference_list *list = referenceListForObject(object, YES); + setReference(list, key, value, policy); +} + +id objc_getAssociatedObject(id object, void *key) +{ + if (isSmallObject(object)) { return nil; } + struct reference_list *list = referenceListForObject(object, NO); + if (NULL == list) { return nil; } + struct reference *r = findReference(list, key); + if (NULL != r) + { + return r->object; + } + if (class_isMetaClass(object->isa)) + { + return nil; + } + Class cls = object->isa; + while (Nil != cls) + { + while (Nil != cls && + !objc_test_class_flag(cls, objc_class_flag_assoc_class)) + { + cls = class_getSuperclass(cls); + } + if (Nil != cls) + { + struct reference_list *next_list = object_getIndexedIvars(cls); + if (list != next_list) + { + list = next_list; + struct reference *r = findReference(list, key); + if (NULL != r) + { + return r->object; + } + } + cls = class_getSuperclass(cls); + } + } + return nil; +} + + +void objc_removeAssociatedObjects(id object) +{ + if (isSmallObject(object)) { return; } + cleanupReferenceList(referenceListForObject(object, NO)); +} + +PRIVATE void *gc_typeForClass(Class cls) +{ + struct reference_list *list = referenceListForObject(cls, YES); + return list->gc_type; +} +PRIVATE void gc_setTypeForClass(Class cls, void *type) +{ + struct reference_list *list = referenceListForObject(cls, YES); + list->gc_type = type; +} + +int objc_sync_enter(id object) +{ + if (isSmallObject(object)) { return 0; } + struct reference_list *list = referenceListForObject(object, YES); + LOCK(&list->lock); + return 0; +} + +int objc_sync_exit(id object) +{ + if (isSmallObject(object)) { return 0; } + struct reference_list *list = referenceListForObject(object, NO); + if (NULL != list) + { + UNLOCK(&list->lock); + return 0; + } + return 1; +} + +static Class hiddenClassForObject(id object) +{ + if (isSmallObject(object)) { return nil; } + if (class_isMetaClass(object->isa)) + { + return object->isa; + } + Class hiddenClass = findHiddenClass(object); + if (NULL == hiddenClass) + { + volatile int *lock = lock_for_pointer(object); + lock_spinlock(lock); + hiddenClass = findHiddenClass(object); + if (NULL == hiddenClass) + { + hiddenClass = initHiddenClassForObject(object); + struct reference_list *list = object_getIndexedIvars(hiddenClass); + INIT_LOCK(list->lock); + } + unlock_spinlock(lock); + } + return hiddenClass; +} + +BOOL object_addMethod_np(id object, SEL name, IMP imp, const char *types) +{ + return class_addMethod(hiddenClassForObject(object), name, imp, types); +} + +IMP object_replaceMethod_np(id object, SEL name, IMP imp, const char *types) +{ + return class_replaceMethod(hiddenClassForObject(object), name, imp, types); +} +static char prototypeKey; + +id object_clone_np(id object) +{ + if (isSmallObject(object)) { return object; } + // Make sure that the prototype has a hidden class, so that methods added + // to it will appear in the clone. + referenceListForObject(object, YES); + id new = class_createInstance(object->isa, 0); + Class hiddenClass = initHiddenClassForObject(new); + struct reference_list *list = object_getIndexedIvars(hiddenClass); + INIT_LOCK(list->lock); + objc_setAssociatedObject(new, &prototypeKey, object, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return new; +} + +id object_getPrototype_np(id object) +{ + return objc_getAssociatedObject(object, &prototypeKey); +} diff --git a/third_party/libobjc/block_to_imp.c b/third_party/libobjc/block_to_imp.c new file mode 100644 index 0000000000000000000000000000000000000000..5b0262c69c4c229a436b5eae40a42a107813c838 --- /dev/null +++ b/third_party/libobjc/block_to_imp.c @@ -0,0 +1,165 @@ +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/mman.h> +#include "objc/runtime.h" +#include "objc/blocks_runtime.h" +#include "blocks_runtime.h" +#include "lock.h" +#include "visibility.h" + + +/* QNX needs a special header for asprintf() */ +#ifdef __QNXNTO__ +#include <nbutil.h> +#endif + +#define PAGE_SIZE 4096 + +static void *executeBuffer; +static void *writeBuffer; +static ptrdiff_t offset; +static mutex_t trampoline_lock; +static char *tmpPattern; + +struct wx_buffer +{ + void *w; + void *x; +}; + +PRIVATE void init_trampolines(void) +{ + INIT_LOCK(trampoline_lock); + char *tmp = getenv("TMPDIR"); + if (NULL == tmp) + { + tmp = "/tmp/"; + } + if (0 > asprintf(&tmpPattern, "%s/objc_trampolinesXXXXXXXXXXX", tmp)) + { + abort(); + } +} + +static struct wx_buffer alloc_buffer(size_t size) +{ + LOCK_FOR_SCOPE(&trampoline_lock); + if ((0 == offset) || (offset + size >= PAGE_SIZE)) + { + int fd = mkstemp(tmpPattern); + unlink(tmpPattern); + ftruncate(fd, PAGE_SIZE); + void *w = mmap(NULL, PAGE_SIZE, PROT_WRITE, MAP_SHARED, fd, 0); + executeBuffer = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0); + *((void**)w) = writeBuffer; + writeBuffer = w; + offset = sizeof(void*); + } + struct wx_buffer b = { writeBuffer + offset, executeBuffer + offset }; + offset += size; + return b; +} + +extern void __objc_block_trampoline; +extern void __objc_block_trampoline_end; +extern void __objc_block_trampoline_sret; +extern void __objc_block_trampoline_end_sret; + +IMP imp_implementationWithBlock(void *block) +{ + struct Block_layout *b = block; + void *start; + void *end; + + if ((b->flags & BLOCK_USE_SRET) == BLOCK_USE_SRET) + { + start = &__objc_block_trampoline_sret; + end = &__objc_block_trampoline_end_sret; + } + else + { + start = &__objc_block_trampoline; + end = &__objc_block_trampoline_end; + } + + size_t trampolineSize = end - start; + // If we don't have a trampoline intrinsic for this architecture, return a + // null IMP. + if (0 >= trampolineSize) { return 0; } + + struct wx_buffer buf = alloc_buffer(trampolineSize + 2*sizeof(void*)); + void **out = buf.w; + out[0] = (void*)b->invoke; + out[1] = Block_copy(b); + memcpy(&out[2], start, trampolineSize); + out = buf.x; + return (IMP)&out[2]; +} + +static void* isBlockIMP(void *anIMP) +{ + LOCK(&trampoline_lock); + void *e = executeBuffer; + void *w = writeBuffer; + UNLOCK(&trampoline_lock); + while (e) + { + if ((anIMP > e) && (anIMP < e + PAGE_SIZE)) + { + return ((char*)w) + ((char*)anIMP - (char*)e); + } + e = *(void**)e; + w = *(void**)w; + } + return 0; +} + +void *imp_getBlock(IMP anImp) +{ + if (0 == isBlockIMP((void*)anImp)) { return 0; } + return *(((void**)anImp) - 1); +} +BOOL imp_removeBlock(IMP anImp) +{ + void *w = isBlockIMP((void*)anImp); + if (0 == w) { return NO; } + Block_release(((void**)anImp) - 1); + return YES; +} + +PRIVATE size_t lengthOfTypeEncoding(const char *types); + +char *block_copyIMPTypeEncoding_np(void*block) +{ + char *buffer = strdup(block_getType_np(block)); + if (NULL == buffer) { return NULL; } + char *replace = buffer; + // Skip the return type + replace += lengthOfTypeEncoding(replace); + while (isdigit(*replace)) { replace++; } + // The first argument type should be @? (block), and we need to transform + // it to @, so we have to delete the ?. Assert here because this isn't a + // block encoding at all if the first argument is not a block, and since we + // got it from block_getType_np(), this means something is badly wrong. + assert('@' == *replace); + replace++; + assert('?' == *replace); + // Use strlen(replace) not replace+1, because we want to copy the NULL + // terminator as well. + memmove(replace, replace+1, strlen(replace)); + // The next argument should be an object, and we want to replace it with a + // selector + while (isdigit(*replace)) { replace++; } + if ('@' != *replace) + { + free(buffer); + return NULL; + } + *replace = ':'; + return buffer; +} diff --git a/third_party/libobjc/block_trampolines.S b/third_party/libobjc/block_trampolines.S new file mode 100644 index 0000000000000000000000000000000000000000..531de55390395839ecc40562559f58147098c468 --- /dev/null +++ b/third_party/libobjc/block_trampolines.S @@ -0,0 +1,81 @@ +# +# This file defines some trampolines for calling blocks. A block function +# looks like this: +# +# retType blockFn(block*, ...) +# +# An IMP looks like this: +# +# retType imp(id, SEL,...) +# +# The trampoline must find the block pointer and then call the block function +# with the correct first argument, the self pointer moved to the second real +# argument (the first block argument) and the _cmd parameter excised + +.file "block_trampolines.S" +#if __arm__ +.syntax unified +.globl __objc_block_trampoline_sret + .type __objc_block_trampoline_sret, %function +.globl __objc_block_trampoline_end_sret +.globl __objc_block_trampoline + .type __objc_block_trampoline, %function +.globl __objc_block_trampoline_end +#else +.globl __objc_block_trampoline_sret + .type __objc_block_trampoline_sret, @function +.globl __objc_block_trampoline_end_sret +.globl __objc_block_trampoline + .type __objc_block_trampoline, @function +.globl __objc_block_trampoline_end +#endif +#if __x86_64 +__objc_block_trampoline: + mov -15(%rip), %rsi # Load the block pointer into the second argument + xchg %rdi, %rsi # Swap the first and second arguments + jmp *-32(%rip) # Call the block function +__objc_block_trampoline_end: +__objc_block_trampoline_sret: + mov -15(%rip), %rdx # Load the block pointer into the second argument + xchg %rdx, %rsi # Swap the first and second arguments + jmp *-32(%rip) # Call the block function +__objc_block_trampoline_end_sret: +#elif __i386 +__objc_block_trampoline: + call next_line # Store the instruction pointer on the stack +next_line: + pop %eax # Load the old instruction pointer + mov 4(%esp), %ebx # Load the self parameter + mov %ebx, 8(%esp) # Store self as the second argument + mov -9(%eax), %ebx # Load the block pointer to %ebx + mov %ebx, 4(%esp) # Store the block pointer in the first argument + jmp *-13(%eax) # Call the block function +__objc_block_trampoline_end: +__objc_block_trampoline_sret: + call next_line2 # Store the instruction pointer on the stack +next_line2: + pop %eax # Load the old instruction pointer + mov 8(%esp), %ebx # Load the self parameter + mov %ebx, 12(%esp) # Store self as the second argument + mov -9(%eax), %ebx # Load the block pointer to %ebx + mov %ebx, 8(%esp) # Store the block pointer in the first argument + jmp *-13(%eax) # Call the block function +__objc_block_trampoline_end_sret: +#elif __arm__ +__objc_block_trampoline: + mov r1, r0 // Move self over _cmd + ldr r0, [pc, #-16] // Load the block pointer over self + ldr pc, [pc, #-24] // Jump to the block function +__objc_block_trampoline_end: +__objc_block_trampoline_sret: + mov r2, r1 // Move self over _cmd + ldr r1, [pc, #-16] // Load the block pointer over self + ldr pc, [pc, #-24] // Jump to the block function +__objc_block_trampoline_end_sret: +#else +#warning imp_implementationWithBlock() not implemented for your architecture +__objc_block_trampoline: +__objc_block_trampoline_end: +__objc_block_trampoline_sret: +__objc_block_trampoline_end_sret: +#endif diff --git a/third_party/libobjc/blocks_runtime.h b/third_party/libobjc/blocks_runtime.h new file mode 100644 index 0000000000000000000000000000000000000000..f69490ae956962d1c67fe371c1db18d74a451ce6 --- /dev/null +++ b/third_party/libobjc/blocks_runtime.h @@ -0,0 +1,125 @@ +/** + * Block descriptor flags. + */ +enum block_flags +{ + /** + * The block descriptor contains copy and dispose helpers. + */ + BLOCK_HAS_COPY_DISPOSE = (1 << 25), + /** + * The helpers have C++ code. + */ + BLOCK_HAS_CTOR = (1 << 26), + /** + * Block is stored in global memory and does not need to be copied. + */ + BLOCK_IS_GLOBAL = (1 << 28), + /** + * Block function uses a calling convention that returns a structure via a + * pointer passed in by the caller. + */ + BLOCK_USE_SRET = (1 << 29), + /** + * Block has an Objective-C type encoding. + */ + BLOCK_HAS_SIGNATURE = (1 << 30), + /** + * Mask for the reference count in byref structure's flags field. The low + * 3 bytes are reserved for the reference count, the top byte for the + * flags. + */ + BLOCK_REFCOUNT_MASK = 0x00ffffff +}; + +/** + * Flags used in the final argument to _Block_object_assign() and + * _Block_object_dispose(). These indicate the type of copy or dispose to + * perform. + */ +enum +{ + /** + * The value is of some id-like type, and should be copied as an + * Objective-C object: i.e. by sending -retain or via the GC assign + * functions in GC mode (not yet supported). + */ + BLOCK_FIELD_IS_OBJECT = 3, + /** + * The field is a block. This must be copied by the block copy functions. + */ + BLOCK_FIELD_IS_BLOCK = 7, + /** + * The field is an indirect reference to a variable declared with the + * __block storage qualifier. + */ + BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable + + BLOCK_FIELD_IS_WEAK = 16, // declared __weak + + BLOCK_BYREF_CALLER = 128, // called from byref copy/dispose helpers +}; +#define IS_SET(x, y) ((x & y) == y) + +/* + * Include the block_descriptor_copydispose and block_literal definitions that + * are also made public under different names for use in libdispatch. + */ +#include "objc/blocks_private.h" + +/** + * Block descriptor that does not contain copy and dispose helper functions. + */ +struct Block_descriptor_basic +{ + /** + * Reserved for future use, currently always 0. + */ + unsigned long int reserved; + /** Size of the block. */ + unsigned long int size; + /** + * Objective-C type encoding of the block. + */ + const char *encoding; +}; + + +/** + * Structure used for on-stack variables that are referenced by blocks. + */ +struct block_byref_obj +{ + /** + * Class pointer. Currently unused and always NULL. Could be used in the + * future to support introspection. + */ + void *isa; + /** + * The pointer to the structure that contains the real version of the data. + * All accesses go via this pointer. If an on-stack byref structure is + * copied to the heap, then its forwarding pointer should point to the heap + * version. Otherwise it should point to itself. + */ + struct block_byref_obj *forwarding; + /** + * Flags and reference count. + */ + int flags; //refcount; + /** + * Size of this structure. + */ + int size; + /** + * Copy function. + */ + void (*byref_keep)(struct block_byref_obj *dst, const struct block_byref_obj *src); + /** + * Dispose function. + */ + void (*byref_dispose)(struct block_byref_obj *); + /** + * __block-qualified variables are copied here. + */ +}; + diff --git a/third_party/libobjc/blocks_runtime.m b/third_party/libobjc/blocks_runtime.m new file mode 100644 index 0000000000000000000000000000000000000000..0ba796129d8a822c92c665805799367c4d3dbba4 --- /dev/null +++ b/third_party/libobjc/blocks_runtime.m @@ -0,0 +1,296 @@ +/* + * 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. + */ +#import "objc/blocks_runtime.h" +#import "objc/runtime.h" +#import "objc/objc-arc.h" +#include "blocks_runtime.h" +#include "gc_ops.h" +#include "visibility.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <assert.h> + + +static void *_HeapBlockByRef = (void*)1; + + +/** + * Returns the Objective-C type encoding for the block. + */ +const char *block_getType_np(void *b) +{ + struct Block_layout *block = b; + if ((NULL == block) || !(block->flags & BLOCK_HAS_SIGNATURE)) + { + return NULL; + } + if (!(block->flags & BLOCK_HAS_COPY_DISPOSE)) + { + return ((struct Block_descriptor_basic*)block->descriptor)->encoding; + } + return block->descriptor->encoding; +} + +static int increment24(int *ref) +{ + int old = *ref; + int val = old & BLOCK_REFCOUNT_MASK; + // FIXME: We should gracefully handle refcount overflow, but for now we + // just give up + assert(val < BLOCK_REFCOUNT_MASK); + if (!__sync_bool_compare_and_swap(ref, old, old+1)) + { + return increment24(ref); + } + return val + 1; +} + +static int decrement24(int *ref) +{ + int old = *ref; + int val = old & BLOCK_REFCOUNT_MASK; + // FIXME: We should gracefully handle refcount overflow, but for now we + // just give up + assert(val > 0); + if (!__sync_bool_compare_and_swap(ref, old, old-1)) + { + return decrement24(ref); + } + return val - 1; +} + +// This is a really ugly hack that works around a buggy register allocator in +// GCC. Compiling nontrivial code using __sync_bool_compare_and_swap() with +// GCC (4.2.1, at least), causes the register allocator to run out of registers +// and fall over and die. We work around this by wrapping this CAS in a +// function, which means the register allocator can trivially handle it. Do +// not remove the noinline attribute - without it, gcc will inline it early on +// and then crash later. +#ifndef __clang__ +__attribute__((noinline)) +static int cas(void *ptr, void *old, void *new) +{ + return __sync_bool_compare_and_swap((void**)ptr, old, new); +} +#define __sync_bool_compare_and_swap cas +#endif + +/* Certain field types require runtime assistance when being copied to the + * heap. The following function is used to copy fields of types: blocks, + * pointers to byref structures, and objects (including + * __attribute__((NSObject)) pointers. BLOCK_FIELD_IS_WEAK is orthogonal to + * the other choices which are mutually exclusive. Only in a Block copy helper + * will one see BLOCK_FIELD_IS_BYREF. + */ +void _Block_object_assign(void *destAddr, const void *object, const int flags) +{ + //printf("Copying %x to %x with flags %x\n", object, destAddr, flags); + // FIXME: Needs to be implemented + //if(flags & BLOCK_FIELD_IS_WEAK) + { + } + //else + { + if (IS_SET(flags, BLOCK_FIELD_IS_BYREF)) + { + struct block_byref_obj *src = (struct block_byref_obj *)object; + struct block_byref_obj **dst = destAddr; + src = src->forwarding; + + if ((src->flags & BLOCK_REFCOUNT_MASK) == 0) + { + *dst = gc->malloc(src->size); + memcpy(*dst, src, src->size); + (*dst)->isa = _HeapBlockByRef; + // Refcount must be two; one for the copy and one for the + // on-stack version that will point to it. + (*dst)->flags += 2; + if (IS_SET(src->flags, BLOCK_HAS_COPY_DISPOSE)) + { + src->byref_keep(*dst, src); + } + (*dst)->forwarding = *dst; + // Concurrency. If we try copying the same byref structure + // from two threads simultaneously, we could end up with two + // versions on the heap that are unaware of each other. That + // would be bad. So we first set up the copy, then try to do + // an atomic compare-and-exchange to point the old version at + // it. If the forwarding pointer in src has changed, then we + // recover - clean up and then return the structure that the + // other thread created. + if (!__sync_bool_compare_and_swap(&src->forwarding, src, *dst)) + { + if((size_t)src->size >= sizeof(struct block_byref_obj)) + { + src->byref_dispose(*dst); + } + gc->free(*dst); + *dst = src->forwarding; + } + } + else + { + *dst = (struct block_byref_obj*)src; + increment24(&(*dst)->flags); + } + } + else if (IS_SET(flags, BLOCK_FIELD_IS_BLOCK)) + { + struct Block_layout *src = (struct Block_layout*)object; + struct Block_layout **dst = destAddr; + + *dst = Block_copy(src); + } + else if (IS_SET(flags, BLOCK_FIELD_IS_OBJECT) && + !IS_SET(flags, BLOCK_BYREF_CALLER)) + { + id src = (id)object; + void **dst = destAddr; + *dst = src; + if (!isGCEnabled) + { + *dst = objc_retain(src); + } + } + } +} + +/* Similarly a compiler generated dispose helper needs to call back for each + * field of the byref data structure. (Currently the implementation only packs + * one field into the byref structure but in principle there could be more). + * The same flags used in the copy helper should be used for each call + * generated to this function: + */ +void _Block_object_dispose(const void *object, const int flags) +{ + // FIXME: Needs to be implemented + //if(flags & BLOCK_FIELD_IS_WEAK) + { + } + //else + { + if (IS_SET(flags, BLOCK_FIELD_IS_BYREF)) + { + struct block_byref_obj *src = + (struct block_byref_obj*)object; + src = src->forwarding; + if (src->isa == _HeapBlockByRef) + { + int refcount = (src->flags & BLOCK_REFCOUNT_MASK) == 0 ? 0 : decrement24(&src->flags); + if (refcount == 0) + { + if(IS_SET(src->flags, BLOCK_HAS_COPY_DISPOSE) && (0 != src->byref_dispose)) + { + src->byref_dispose(src); + } + gc->free(src); + } + } + } + else if (IS_SET(flags, BLOCK_FIELD_IS_BLOCK)) + { + struct Block_layout *src = (struct Block_layout*)object; + Block_release(src); + } + else if (IS_SET(flags, BLOCK_FIELD_IS_OBJECT) && + !IS_SET(flags, BLOCK_BYREF_CALLER)) + { + id src = (id)object; + if (!isGCEnabled) + { + objc_release(src); + } + } + } +} + + +// Copy a block to the heap if it's still on the stack or increments its retain count. +void *_Block_copy(void *src) +{ + if (NULL == src) { return NULL; } + struct Block_layout *self = src; + struct Block_layout *ret = self; + + extern void _NSConcreteStackBlock; + extern void _NSConcreteMallocBlock; + + // If the block is Global, there's no need to copy it on the heap. + if(self->isa == &_NSConcreteStackBlock) + { + ret = gc->malloc(self->descriptor->size); + memcpy(ret, self, self->descriptor->size); + ret->isa = &_NSConcreteMallocBlock; + if(self->flags & BLOCK_HAS_COPY_DISPOSE) + { + self->descriptor->copy_helper(ret, self); + } + // We don't need any atomic operations here, because on-stack blocks + // can not be aliased across threads (unless you've done something + // badly wrong). + ret->reserved = 1; + } + else if (self->isa == &_NSConcreteMallocBlock) + { + // We need an atomic increment for malloc'd blocks, because they may be + // shared. + __sync_fetch_and_add(&ret->reserved, 1); + } + return ret; +} + +// Release a block and frees the memory when the retain count hits zero. +void _Block_release(void *src) +{ + if (NULL == src) { return; } + struct Block_layout *self = src; + + extern void _NSConcreteStackBlock; + extern void _NSConcreteMallocBlock; + + if (&_NSConcreteStackBlock == self->isa) + { + fprintf(stderr, "Block_release called upon a stack Block: %p, ignored\n", self); + } + else if (&_NSConcreteMallocBlock == self->isa) + { + if (__sync_sub_and_fetch(&self->reserved, 1) == 0) + { + if(self->flags & BLOCK_HAS_COPY_DISPOSE) + self->descriptor->dispose_helper(self); + objc_delete_weak_refs((id)self); + gc->free(self); + } + } +} + +PRIVATE void* block_load_weak(void *block) +{ + struct Block_layout *self = block; + return (self->reserved) > 0 ? block : 0; +} diff --git a/third_party/libobjc/buffer.h b/third_party/libobjc/buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..4658ee127f1b362a440211bc2507571200edc09a --- /dev/null +++ b/third_party/libobjc/buffer.h @@ -0,0 +1,62 @@ +/** + * buffer.h defines a simple dynamic array that is used to store temporary + * values for later processing. Define BUFFER_TYPE before including this file. + */ + +#include <stdlib.h> + +#define BUFFER_SIZE 128 +static BUFFER_TYPE *buffered_object_buffer[BUFFER_SIZE]; +static BUFFER_TYPE **buffered_object_overflow; +static int buffered_objects; +static int buffered_object_overflow_space; + +static void set_buffered_object_at_index(BUFFER_TYPE *cat, unsigned int i) +{ + if (i < BUFFER_SIZE) + { + buffered_object_buffer[i] = cat; + } + else + { + i -= BUFFER_SIZE; + if (NULL == buffered_object_overflow) + { + buffered_object_overflow = + calloc(BUFFER_SIZE, sizeof(BUFFER_TYPE*)); + buffered_object_overflow_space = BUFFER_SIZE; + } + while (i >= buffered_object_overflow_space) + { + buffered_object_overflow_space <<= 1; + buffered_object_overflow = realloc(buffered_object_overflow, + buffered_object_overflow_space * sizeof(BUFFER_TYPE*)); + } + buffered_object_overflow[i] = cat; + } +} + +static BUFFER_TYPE *buffered_object_at_index(unsigned int i) +{ + if (i<BUFFER_SIZE) + { + return buffered_object_buffer[i]; + } + return buffered_object_overflow[i-BUFFER_SIZE]; +} + +static void compact_buffer(void) +{ + // Move up all of the non-NULL pointers + unsigned size = buffered_objects; + unsigned insert = 0; + for (unsigned i=0 ; i<size ; i++) + { + BUFFER_TYPE *c = buffered_object_at_index(i); + if (c != NULL) + { + set_buffered_object_at_index(c, insert++); + } + } + buffered_objects = insert; +} diff --git a/third_party/libobjc/build_opts.sh b/third_party/libobjc/build_opts.sh new file mode 100644 index 0000000000000000000000000000000000000000..2aea6c10667ac0d104c769f27171f56d19d52e93 --- /dev/null +++ b/third_party/libobjc/build_opts.sh @@ -0,0 +1,20 @@ +#!/bin/sh +LLVM_PATH=`llvm-config --src-root` +LIBOBJC_PATH=`pwd` +if [ x$LLVM_PATH != x ] ; then + if [ -d $LLVM_PATH ] ; then + cd $LLVM_PATH + cd lib/Transforms + if [ ! -d GNURuntime ] ; then + mkdir GNURuntime + fi + cd GNURuntime + for I in `ls $LIBOBJC_PATH/opts/` ; do + if [ ! $I -nt $LIBOBJC_PATH/opts/$I ] ; then + cp $LIBOBJC_PATH/opts/$I . + fi + done + $1 $2 + cd .. + fi +fi diff --git a/third_party/libobjc/caps.c b/third_party/libobjc/caps.c new file mode 100644 index 0000000000000000000000000000000000000000..5010ff1b1a888db8068deeedc986bbd3ed08401f --- /dev/null +++ b/third_party/libobjc/caps.c @@ -0,0 +1,39 @@ +#include "objc/capabilities.h" +#include <stdint.h> + +/** + * Bitmask of all of the capabilities compiled into this version of the + * runtime. + */ +static const int32_t caps = + (1<<OBJC_CAP_EXCEPTIONS) | + (1<<OBJC_CAP_SYNCRONIZE) | + (1<<OBJC_CAP_PROPERTIES) | + (1<<OBJC_CAP_PROPERTY_INTROSPECTION) | + (1<<OBJC_CAP_OPTIONAL_PROTOCOLS) | + (1<<OBJC_CAP_NONFRAGILE_IVARS) | + (1<<OBJC_DEVELOPER_MODE) | + (1<<OBJC_CAP_REGISTERED_COMPATIBILITY_ALIASES) | + (1<<OBJC_CAP_ARC) | + (1<<OBJC_CAP_ASSOCIATED_REFERENCES) | + (1<<OBJC_CAP_PROTOTYPES) | +#ifndef NO_OBJCXX + (1<<OBJC_UNIFIED_EXCEPTION_MODEL) | +#endif +#ifdef TYPE_DEPENDENT_DISPATCH + (1<<OBJC_CAP_TYPE_DEPENDENT_DISPATCH) | +#endif +#ifdef __OBJC_LOW_MEMORY__ + (1<<OBJC_CAP_LOW_MEMORY) | +#endif +#ifdef ENABLE_GC + (1<<OBJC_CAP_GARBAGE_COLLECTION) | +#endif + 0; + +int objc_test_capability(int x) +{ + if (x >= 32) { return 0; } + if (caps & (1<<x)) { return 1; } + return 0; +} diff --git a/third_party/libobjc/category.h b/third_party/libobjc/category.h new file mode 100644 index 0000000000000000000000000000000000000000..44eb892e6daa750941eab447e4793887a9454839 --- /dev/null +++ b/third_party/libobjc/category.h @@ -0,0 +1,35 @@ + +/** + * The structure used to represent a category. + * + * This provides a set of new definitions that are used to replace those + * contained within a class. + * + * Note: Objective-C 2 allows properties to be added to classes. The current + * ABI does not provide a field for adding properties in categories. This is + * likely to be added with ABI version 10. Until then, the methods created by + * a declared property will work, but introspection on the property will not. + */ +struct objc_category +{ + /** + * The name of this category. + */ + const char *name; + /** + * The name of the class to which this category should be applied. + */ + const char *class_name; + /** + * The list of instance methods to add to the class. + */ + struct objc_method_list *instance_methods; + /** + * The list of class methods to add to the class. + */ + struct objc_method_list *class_methods; + /** + * The list of protocols adopted by this category. + */ + struct objc_protocol_list *protocols; +}; diff --git a/third_party/libobjc/category_loader.c b/third_party/libobjc/category_loader.c new file mode 100644 index 0000000000000000000000000000000000000000..cb0e020adace2bfe41c369085fa480e403a16c89 --- /dev/null +++ b/third_party/libobjc/category_loader.c @@ -0,0 +1,91 @@ +#include <stdio.h> +#include "objc/runtime.h" +#include "visibility.h" +#include "loader.h" +#include "dtable.h" + +#define BUFFER_TYPE struct objc_category +#include "buffer.h" + +void objc_send_load_message(Class class); + +static void register_methods(struct objc_class *cls, struct objc_method_list *l) +{ + if (NULL == l) { return; } + + // Replace the method names with selectors. + objc_register_selectors_from_list(l); + // Add the method list at the head of the list of lists. + l->next = cls->methods; + cls->methods = l; + // Update the dtable to catch the new methods, if the dtable has been + // created (don't bother creating dtables for classes when categories are + // loaded if the class hasn't received any messages yet. + if (classHasDtable(cls)) + { + add_method_list_to_class(cls, l); + } +} + +static void load_category(struct objc_category *cat, struct objc_class *class) +{ + register_methods(class, cat->instance_methods); + register_methods(class->isa, cat->class_methods); + //fprintf(stderr, "Loading %s (%s)\n", cat->class_name, cat->name); + + if (cat->protocols) + { + objc_init_protocols(cat->protocols); + cat->protocols->next = class->protocols; + class->protocols = cat->protocols; + } +} + +static BOOL try_load_category(struct objc_category *cat) +{ + Class class = (Class)objc_getClass(cat->class_name); + //fprintf(stderr, "Trying to load %s (%s)\n", cat->class_name, cat->name); + if (Nil != class) + { + load_category(cat, class); + return YES; + } + //fprintf(stderr, "waiting to load %s (%s)\n", cat->class_name, cat->name); + return NO; +} + +/** + * Attaches a category to its class, if the class is already loaded. Buffers + * it for future resolution if not. + */ +PRIVATE void objc_try_load_category(struct objc_category *cat) +{ + if (!try_load_category(cat)) + { + set_buffered_object_at_index(cat, buffered_objects++); + } +} + +PRIVATE void objc_load_buffered_categories(void) +{ + BOOL shouldReshuffle = NO; + + for (unsigned i=0 ; i<buffered_objects ; i++) + { + struct objc_category *c = buffered_object_at_index(i); + if (NULL != c) + { + if (try_load_category(c)) + { + set_buffered_object_at_index(NULL, i); + shouldReshuffle = YES; + } + } + } + + if (shouldReshuffle) + { + compact_buffer(); + } +} + diff --git a/third_party/libobjc/class.h b/third_party/libobjc/class.h new file mode 100644 index 0000000000000000000000000000000000000000..e37ab8b3c7c8018746e536290df138aabfafd281 --- /dev/null +++ b/third_party/libobjc/class.h @@ -0,0 +1,288 @@ +#ifndef __OBJC_CLASS_H_INCLUDED +#define __OBJC_CLASS_H_INCLUDED +#include "visibility.h" + +/** + * Overflow bitfield. Used for bitfields that are more than 63 bits. + */ +struct objc_bitfield +{ + /** + * The number of elements in the values array. + */ + int32_t length; + /** + * An array of values. Each 32 bits is stored in the native endian for the + * platform. + */ + int32_t values[0]; +}; + +struct objc_class +{ + /** + * Pointer to the metaclass for this class. The metaclass defines the + * methods use when a message is sent to the class, rather than an + * instance. + */ + struct objc_class *isa; + /** + * Pointer to the superclass. The compiler will set this to the name of + * the superclass, the runtime will initialize it to point to the real + * class. + */ + struct objc_class *super_class; + /** + * The name of this class. Set to the same value for both the class and + * its associated metaclass. + */ + const char *name; + /** + * The version of this class. This is not used by the language, but may be + * set explicitly at class load time. + */ + long version; + /** + * A bitfield containing various flags. See the objc_class_flags + * enumerated type for possible values. + */ + unsigned long info; + /** + * The size of this class. For classes using the non-fragile ABI, the + * compiler will set this to a negative value The absolute value will be + * the size of the instance variables defined on just this class. When + * using the fragile ABI, the instance size is the size of instances of + * this class, including any instance variables defined on superclasses. + * + * In both cases, this will be set to the size of an instance of the class + * after the class is registered with the runtime. + */ + long instance_size; + /** + * Metadata describing the instance variables in this class. + */ + struct objc_ivar_list *ivars; + /** + * Metadata for for defining the mappings from selectors to IMPs. Linked + * list of method list structures, one per class and one per category. + */ + struct objc_method_list *methods; + /** + * The dispatch table for this class. Intialized and maintained by the + * runtime. + */ + void *dtable; + /** + * A pointer to the first subclass for this class. Filled in by the + * runtime. + */ + struct objc_class *subclass_list; + /** + * A pointer to the next sibling class to this. You may find all + * subclasses of a given class by following the subclass_list pointer and + * then subsequently following the sibling_class pointers in the + * subclasses. + */ + struct objc_class *sibling_class; + + /** + * Metadata describing the protocols adopted by this class. Not used by + * the runtime. + */ + struct objc_protocol_list *protocols; + /** + * Linked list of extra data attached to this class. + */ + struct reference_list *extra_data; + /** + * New ABI. The following fields are only available with classes compiled to + * support the new ABI. You may test whether any given class supports this + * ABI by using the CLS_ISNEW_ABI() macro. + */ + + /** + * The version of the ABI used for this class. Zero indicates the ABI first + * implemented by clang 1.0. One indicates the presence of bitmaps + * indicating the offsets of strong, weak, and unretained ivars. + */ + long abi_version; + + /** + * Array of pointers to variables where the runtime will store the ivar + * offset. These may be used for faster access to non-fragile ivars if all + * of the code is compiled for the new ABI. Each of these pointers should + * have the mangled name __objc_ivar_offset_value_{class name}.{ivar name} + * + * When using the compatible non-fragile ABI, this faster form should only be + * used for classes declared in the same compilation unit. + * + * The compiler should also emit symbols of the form + * __objc_ivar_offset_{class name}.{ivar name} which are pointers to the + * offset values. These should be emitted as weak symbols in every module + * where they are used. The legacy-compatible ABI uses these with a double + * layer of indirection. + */ + int **ivar_offsets; + /** + * List of declared properties on this class (NULL if none). This contains + * the accessor methods for each property. + */ + struct objc_property_list *properties; + + /** + * GC / ARC ABI: Fields below this point only exist if abi_version is >= 1. + */ + + /** + * The location of all strong pointer ivars declared by this class. + * + * If the low bit of this field is 0, then this is a pointer to an + * objc_bitfield structure. If the low bit is 1, then the remaining 63 + * bits are set, from low to high, for each ivar in the object that is a + * strong pointer. + */ + intptr_t strong_pointers; + /** + * The location of all zeroing weak pointer ivars declared by this class. + * The format of this field is the same as the format of the + * strong_pointers field. + */ + intptr_t weak_pointers; +}; + +/** + * Structure representing the old ABI class structure. This is only ever + * required so that we can take its size - struct objc_class begins with the + * same fields, and you can test the new abi flag to tell whether it is safe to + * access the subsequent fields. + */ +struct legacy_abi_objc_class +{ + struct objc_class *isa; + struct objc_class *super_class; + const char *name; + long version; + unsigned long info; + long instance_size; + struct objc_ivar_list *ivars; + struct objc_method_list *methods; + void *dtable; + struct objc_class *subclass_list; + struct objc_class *sibling_class; + struct objc_protocol_list *protocols; + void *gc_object_type; +}; + + +/** + * An enumerated type describing all of the valid flags that may be used in the + * info field of a class. + */ +enum objc_class_flags +{ + /** This class structure represents a class. */ + objc_class_flag_class = (1<<0), + /** This class structure represents a metaclass. */ + objc_class_flag_meta = (1<<1), + /** + * This class has been sent a +initalize message. This message is sent + * exactly once to every class that is sent a message by the runtime, just + * before the first other message is sent. + */ + objc_class_flag_initialized = (1<<2), + /** + * The class has been initialized by the runtime. Its super_class pointer + * should now point to a class, rather than a C string containing the class + * name, and its subclass and sibling class links will have been assigned, + * if applicable. + */ + objc_class_flag_resolved = (1<<3), + /** + * The class uses the new, Objective-C 2, runtime ABI. This ABI defines an + * ABI version field inside the class, and so will be used for all + * subsequent versions that retain some degree of compatibility. + */ + objc_class_flag_new_abi = (1<<4), + /** + * This class was created at run time and may be freed. + */ + objc_class_flag_user_created = (1<<5), + /** + * Instances of this class are provide ARC-safe retain / release / + * autorelease implementations. + */ + objc_class_flag_fast_arc = (1<<6), + /** + * This class is a hidden class (should not be registered in the class + * table nor returned from object_getClass()). + */ + objc_class_flag_hidden_class = (1<<7), + /** + * This class is a hidden class used to store associated values. + */ + objc_class_flag_assoc_class = (1<<8) +}; + +/** + * Sets the specific class flag. Note: This is not atomic. + */ +static inline void objc_set_class_flag(struct objc_class *aClass, + enum objc_class_flags flag) +{ + aClass->info |= (unsigned long)flag; +} +/** + * Unsets the specific class flag. Note: This is not atomic. + */ +static inline void objc_clear_class_flag(struct objc_class *aClass, + enum objc_class_flags flag) +{ + aClass->info &= ~(unsigned long)flag; +} +/** + * Checks whether a specific class flag is set. + */ +static inline BOOL objc_test_class_flag(struct objc_class *aClass, + enum objc_class_flags flag) +{ + return (aClass->info & (unsigned long)flag) == (unsigned long)flag; +} + +/** + * Adds a class to the class table. + */ +void class_table_insert(Class class); + +/** + * Array of classes used for small objects. Small objects are embedded in + * their pointer. In 32-bit mode, we have one small object class (typically + * used for storing 31-bit signed integers. In 64-bit mode then we can have 7, + * because classes are guaranteed to be word aligned. + */ +extern Class SmallObjectClasses[7]; + +static BOOL isSmallObject(id obj) +{ + uintptr_t addr = ((uintptr_t)obj); + return (addr & OBJC_SMALL_OBJECT_MASK) != 0; +} + +__attribute__((always_inline)) +static inline Class classForObject(id obj) +{ + if (UNLIKELY(isSmallObject(obj))) + { + if (sizeof(Class) == 4) + { + return SmallObjectClasses[0]; + } + else + { + uintptr_t addr = ((uintptr_t)obj); + return SmallObjectClasses[(addr & OBJC_SMALL_OBJECT_MASK)]; + } + } + return obj->isa; +} + +#endif //__OBJC_CLASS_H_INCLUDED diff --git a/third_party/libobjc/class_table.c b/third_party/libobjc/class_table.c new file mode 100644 index 0000000000000000000000000000000000000000..8feb1f4890f97db32e303ef314133e27e8c3533b --- /dev/null +++ b/third_party/libobjc/class_table.c @@ -0,0 +1,553 @@ +#include "objc/runtime.h" +#include "objc/hooks.h" +#include "objc/developer.h" +#include "alias.h" +#include "class.h" +#include "method_list.h" +#include "selector.h" +#include "lock.h" +#include "ivar.h" +#include "dtable.h" +#include "visibility.h" +#include <stdlib.h> +#include <assert.h> + +void objc_register_selectors_from_class(Class class); +void objc_init_protocols(struct objc_protocol_list *protos); +void objc_compute_ivar_offsets(Class class); + +//////////////////////////////////////////////////////////////////////////////// +// +load method hash table +//////////////////////////////////////////////////////////////////////////////// +static int imp_compare(const void *i1, void *i2) +{ + return i1 == i2; +} +static int32_t imp_hash(const void *imp) +{ + return (int32_t)(((uintptr_t)imp) >> 4); +} +#define MAP_TABLE_NAME load_messages +#define MAP_TABLE_COMPARE_FUNCTION imp_compare +#define MAP_TABLE_HASH_KEY imp_hash +#define MAP_TABLE_HASH_VALUE imp_hash +#include "hash_table.h" + +static load_messages_table *load_table; + +SEL loadSel; + +PRIVATE void objc_init_load_messages_table(void) +{ + load_messages_initialize(&load_table, 4096); + loadSel = sel_registerName("load"); +} + +PRIVATE void objc_send_load_message(Class class) +{ + Class meta = class->isa; + for (struct objc_method_list *l=meta->methods ; NULL!=l ; l=l->next) + { + for (int i=0 ; i<l->count ; i++) + { + Method m = &l->methods[i]; + if (sel_isEqual(m->selector, loadSel)) + { + if (load_messages_table_get(load_table, m->imp) == 0) + { + m->imp((id)class, loadSel); + load_messages_insert(load_table, m->imp); + } + } + } + } +} + +// Get the functions for string hashing +#include "string_hash.h" + +static int class_compare(const char *name, const Class class) +{ + return string_compare(name, class->name); +} +static int class_hash(const Class class) +{ + return string_hash(class->name); +} +#define MAP_TABLE_NAME class_table_internal +#define MAP_TABLE_COMPARE_FUNCTION class_compare +#define MAP_TABLE_HASH_KEY string_hash +#define MAP_TABLE_HASH_VALUE class_hash +// This defines the maximum number of classes that the runtime supports. +/* +#define MAP_TABLE_STATIC_SIZE 2048 +#define MAP_TABLE_STATIC_NAME class_table +*/ +#include "hash_table.h" + +static class_table_internal_table *class_table; + + +#define unresolved_class_next subclass_list +#define unresolved_class_prev sibling_class +/** + * Linked list using the subclass_list pointer in unresolved classes. + */ +static Class unresolved_class_list; + +static enum objc_developer_mode_np mode; + +void objc_setDeveloperMode_np(enum objc_developer_mode_np newMode) +{ + mode = newMode; +} + +//////////////////////////////////////////////////////////////////////////////// +// Class table manipulation +//////////////////////////////////////////////////////////////////////////////// + +PRIVATE Class zombie_class; + +PRIVATE void class_table_insert(Class class) +{ + if (!objc_test_class_flag(class, objc_class_flag_resolved)) + { + if (Nil != unresolved_class_list) + { + unresolved_class_list->unresolved_class_prev = class; + } + class->unresolved_class_next = unresolved_class_list; + unresolved_class_list = class; + } + if ((0 == zombie_class) && (strcmp("NSZombie", class->name) == 0)) + { + zombie_class = class; + } + class_table_internal_insert(class_table, class); +} + +PRIVATE Class class_table_get_safe(const char *class_name) +{ + if (NULL == class_name) { return Nil; } + return class_table_internal_table_get(class_table, class_name); +} + +PRIVATE Class class_table_next(void **e) +{ + return class_table_internal_next(class_table, + (struct class_table_internal_table_enumerator**)e); +} + +PRIVATE void init_class_tables(void) +{ + class_table_internal_initialize(&class_table, 4096); + objc_init_load_messages_table(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Loader functions +//////////////////////////////////////////////////////////////////////////////// + +PRIVATE BOOL objc_resolve_class(Class cls) +{ + // Skip this if the class is already resolved. + if (objc_test_class_flag(cls, objc_class_flag_resolved)) { return YES; } + + // We can only resolve the class if its superclass is resolved. + if (cls->super_class) + { + Class super = (Class)objc_getClass((char*)cls->super_class); + if (Nil == super) { return NO; } + + if (!objc_test_class_flag(super, objc_class_flag_resolved)) + { + if (!objc_resolve_class(super)) + { + return NO; + } + } + } + + + // Remove the class from the unresolved class list + if (Nil == cls->unresolved_class_prev) + { + unresolved_class_list = cls->unresolved_class_next; + } + else + { + cls->unresolved_class_prev->unresolved_class_next = + cls->unresolved_class_next; + } + if (Nil != cls->unresolved_class_next) + { + cls->unresolved_class_next->unresolved_class_prev = + cls->unresolved_class_prev; + } + cls->unresolved_class_prev = Nil; + cls->unresolved_class_next = Nil; + + // The superclass for the metaclass. This is the metaclass for the + // superclass if one exists, otherwise it is the root class itself + Class superMeta = Nil; + // The metaclass for the metaclass. This is always the root class's + // metaclass. + Class metaMeta = Nil; + + // Resolve the superclass pointer + + if (NULL == cls->super_class) + { + superMeta = cls; + metaMeta = cls->isa; + } + else + { + // Resolve the superclass if it isn't already resolved + Class super = (Class)objc_getClass((char*)cls->super_class); + if (!objc_test_class_flag(super, objc_class_flag_resolved)) + { + objc_resolve_class(super); + } + superMeta = super->isa; + // Set the superclass pointer for the class and the superclass + cls->super_class = super; + do + { + metaMeta = super->isa; + super = super->super_class; + } while (Nil != super); + } + Class meta = cls->isa; + + // Make the root class the superclass of the metaclass (e.g. NSObject is + // the superclass of all metaclasses in classes that inherit from NSObject) + meta->super_class = superMeta; + meta->isa = metaMeta; + + // Don't register root classes as children of anything + if (Nil != cls->super_class) + { + // Set up the class links + cls->sibling_class = cls->super_class->subclass_list; + cls->super_class->subclass_list = cls; + } + // Set up the metaclass links + meta->sibling_class = superMeta->subclass_list; + superMeta->subclass_list = meta; + + // Mark this class (and its metaclass) as resolved + objc_set_class_flag(cls, objc_class_flag_resolved); + objc_set_class_flag(cls->isa, objc_class_flag_resolved); + + + // Fix up the ivar offsets + objc_compute_ivar_offsets(cls); + // Send the +load message, if required + objc_send_load_message(cls); + if (_objc_load_callback) + { + _objc_load_callback(cls, 0); + } + return YES; +} + +PRIVATE void objc_resolve_class_links(void) +{ + LOCK_RUNTIME_FOR_SCOPE(); + Class class = unresolved_class_list; + BOOL resolvedClass; + do + { + resolvedClass = NO; + while ((Nil != class)) + { + Class next = class->unresolved_class_next; + objc_resolve_class(class); + if (resolvedClass || + objc_test_class_flag(class, objc_class_flag_resolved)) + { + resolvedClass = YES; + } + class = next; + } + } while (resolvedClass); +} +void __objc_resolve_class_links(void) +{ + static BOOL warned = NO; + if (!warned) + { + fprintf(stderr, + "Warning: Calling deprecated private ObjC runtime function %s\n", __func__); + warned = YES; + } + objc_resolve_class_links(); +} + +static void reload_class(struct objc_class *class, struct objc_class *old) +{ + const char *superclassName = (char*)class->super_class; + class->super_class = class_table_get_safe(superclassName); + // Checking the instance sizes are equal here is a quick-and-dirty test. + // It's not actually needed, because we're testing the ivars are at the + // same locations next, but it lets us skip those tests if the total size + // is different. + BOOL equalLayouts = (class->super_class == old->super_class) && + (class->instance_size == old->instance_size); + // If either of the classes has an empty ivar list, then the other one must too. + if ((NULL == class->ivars) || (NULL == old->ivars)) + { + equalLayouts &= (class->ivars == old->ivars); + } + else + { + // If the class sizes are the same, ensure that the ivars have the same + // types, names, and offsets. Note: Renaming an ivar is treated as a + // conflict because name changes are often accompanied by semantic + // changes. For example, an object ivar at offset 16 goes from being + // called 'delegate' to being called 'view' - we almost certainly don't + // want methods that expect to be working with the delegate ivar to + // work with the view ivar now! + for (int i=0 ; equalLayouts && (i<old->ivars->count) ; i++) + { + struct objc_ivar *oldIvar = &old->ivars->ivar_list[i]; + struct objc_ivar *newIvar = &class->ivars->ivar_list[i]; + equalLayouts &= strcmp(oldIvar->name, newIvar->name) == 0; + equalLayouts &= strcmp(oldIvar->type, newIvar->type) == 0; + equalLayouts &= (oldIvar->offset == newIvar->offset); + } + } + + // If the layouts are equal, then we can simply tack the class's method + // list on to the front of the old class and update the dtable. + if (equalLayouts) + { + class->methods->next = old->methods; + old->methods = class->methods; + objc_update_dtable_for_class(old); + return; + } + + // If we get to here, then we are adding a new class. This is where things + // start to get a bit tricky... + + // Ideally, we'd want to capture the subclass list here. Unfortunately, + // this is not possible because the subclass will contain methods that + // refer to ivars in the superclass. + // + // We can't use the non-fragile ABI's offset facility easily, because we'd + // have to have two (or more) offsets for the same ivar. This gets messy + // very quickly. Ideally, we'd want every class to include ivar offsets + // for every single (public) ivar in its superclasses. These could then be + // updated by copies of the class. Defining a development ABI is something + // to consider for a future release. + class->subclass_list = NULL; + + // Replace the old class with this one in the class table. New lookups for + // this class will now return this class. + class_table_internal_table_set(class_table, (void*)class->name, class); + + // Register all of the selectors used by this class and its metaclass + objc_register_selectors_from_class(class); + objc_register_selectors_from_class(class->isa); + + // Set the uninstalled dtable. The compiler could do this as well. + class->dtable = uninstalled_dtable; + class->isa->dtable = uninstalled_dtable; + + // If this is a root class, make the class into the metaclass's superclass. + // This means that all instance methods will be available to the class. + if (NULL == superclassName) + { + class->isa->super_class = class; + } + + if (class->protocols) + { + objc_init_protocols(class->protocols); + } +} + +/** + * Loads a class. This function assumes that the runtime mutex is locked. + */ +PRIVATE void objc_load_class(struct objc_class *class) +{ + struct objc_class *existingClass = class_table_get_safe(class->name); + if (Nil != existingClass) + { + if (objc_developer_mode_developer != mode) + { + fprintf(stderr, + "Loading two versions of %s. The class that will be used is undefined\n", + class->name); + return; + } + reload_class(class, existingClass); + return; + } + + // The compiler initialises the super class pointer to the name of the + // superclass, not the superclass pointer. + // Note: With the new ABI, the class pointer is public. We could, + // therefore, directly reference the superclass from the compiler and make + // the linker resolve it. This should be done in the GCC-incompatible ABI. + const char *superclassName = (char*)class->super_class; + + // Work around a bug in some versions of GCC that don't initialize the + // class structure correctly. + class->subclass_list = NULL; + + // Insert the class into the class table + class_table_insert(class); + + // Register all of the selectors used by this class and its metaclass + objc_register_selectors_from_class(class); + objc_register_selectors_from_class(class->isa); + + // Set the uninstalled dtable. The compiler could do this as well. + class->dtable = uninstalled_dtable; + class->isa->dtable = uninstalled_dtable; + + // If this is a root class, make the class into the metaclass's superclass. + // This means that all instance methods will be available to the class. + if (NULL == superclassName) + { + class->isa->super_class = class; + } + + if (class->protocols) + { + objc_init_protocols(class->protocols); + } +} + +PRIVATE Class SmallObjectClasses[7]; + +BOOL objc_registerSmallObjectClass_np(Class class, uintptr_t mask) +{ + if ((mask & OBJC_SMALL_OBJECT_MASK) != mask) + { + return NO; + } + if (sizeof(void*) == 4) + { + if (Nil == SmallObjectClasses[0]) + { + SmallObjectClasses[0] = class; + return YES; + } + return NO; + } + if (Nil != SmallObjectClasses[mask]) + { + return NO; + } + SmallObjectClasses[mask] = class; + return YES; +} + + +//////////////////////////////////////////////////////////////////////////////// +// Public API +//////////////////////////////////////////////////////////////////////////////// + +int objc_getClassList(Class *buffer, int bufferLen) +{ + if (buffer == NULL || bufferLen == 0) + { + return class_table->table_used; + } + int count = 0; + struct class_table_internal_table_enumerator *e = NULL; + Class next; + while (count < bufferLen && + (next = class_table_internal_next(class_table, &e))) + { + buffer[count++] = next; + } + return count; +} +Class *objc_copyClassList(unsigned int *outCount) +{ + int count = class_table->table_used; + Class *buffer = calloc(sizeof(Class), count); + if (NULL != outCount) + { + *outCount = count; + } + objc_getClassList(buffer, count); + return buffer; +} + +Class class_getSuperclass(Class cls) +{ + if (Nil == cls) { return Nil; } + if (!objc_test_class_flag(cls, objc_class_flag_resolved)) + { + objc_resolve_class(cls); + } + return cls->super_class; +} + + +id objc_getClass(const char *name) +{ + id class = (id)class_table_get_safe(name); + + if (nil != class) { return class; } + + // Second chance lookup via @compatibilty_alias: + class = (id)alias_getClass(name); + if (nil != class) { return class; } + + // Third chance lookup via the hook: + if (0 != _objc_lookup_class) + { + class = (id)_objc_lookup_class(name); + } + + return class; +} + +id objc_lookUpClass(const char *name) +{ + return (id)class_table_get_safe(name); +} + + +id objc_getMetaClass(const char *name) +{ + Class cls = (Class)objc_getClass(name); + return cls == Nil ? nil : (id)cls->isa; +} + +// Legacy interface compatibility + +id objc_get_class(const char *name) +{ + return objc_getClass(name); +} + +id objc_lookup_class(const char *name) +{ + return objc_getClass(name); +} + +id objc_get_meta_class(const char *name) +{ + return objc_getMetaClass(name); +} + +Class objc_next_class(void **enum_state) +{ + return class_table_next ( enum_state); +} + +Class class_pose_as(Class impostor, Class super_class) +{ + fprintf(stderr, "Class posing is no longer supported.\n"); + fprintf(stderr, "Please use class_replaceMethod() instead.\n"); + abort(); +} diff --git a/third_party/libobjc/constant_string.h b/third_party/libobjc/constant_string.h new file mode 100644 index 0000000000000000000000000000000000000000..781a491c506d7bd283efbfcbe754309e18cbb617 --- /dev/null +++ b/third_party/libobjc/constant_string.h @@ -0,0 +1,7 @@ +#ifndef CONSTANT_STRING_CLASS +# ifdef GNUSTEP +# define CONSTANT_STRING_CLASS "NSConstantString" +# else +# define CONSTANT_STRING_CLASS "NXConstantString" +# endif +#endif diff --git a/third_party/libobjc/dtable.c b/third_party/libobjc/dtable.c new file mode 100644 index 0000000000000000000000000000000000000000..a22351188771e22c0dea83e0db4b27377dcf7689 --- /dev/null +++ b/third_party/libobjc/dtable.c @@ -0,0 +1,734 @@ +#define __BSD_VISIBLE 1 +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include "objc/runtime.h" +#include "sarray2.h" +#include "selector.h" +#include "class.h" +#include "lock.h" +#include "method_list.h" +#include "slot_pool.h" +#include "dtable.h" +#include "visibility.h" + +PRIVATE dtable_t uninstalled_dtable; + +/** Head of the list of temporary dtables. Protected by initialize_lock. */ +PRIVATE InitializingDtable *temporary_dtables; +/** Lock used to protect the temporary dtables list. */ +PRIVATE mutex_t initialize_lock; +/** The size of the largest dtable, rounded up to the nearest power of two. */ +static uint32_t dtable_depth = 8; + +struct objc_slot* objc_get_slot(Class cls, SEL selector); + +/** + * Returns YES if the class implements a method for the specified selector, NO + * otherwise. + */ +static BOOL ownsMethod(Class cls, SEL sel) +{ + struct objc_slot *slot = objc_get_slot(cls, sel); + if ((NULL != slot) && (slot->owner == cls)) + { + return YES; + } + return NO; +} + +/** + * Checks whether the class implements memory management methods, and whether + * they are safe to use with ARC. + */ +static void checkARCAccessors(Class cls) +{ + static SEL retain, release, autorelease, isARC; + if (NULL == retain) + { + retain = sel_registerName("retain"); + release = sel_registerName("release"); + autorelease = sel_registerName("autorelease"); + isARC = sel_registerName("_ARCCompliantRetainRelease"); + } + struct objc_slot *slot = objc_get_slot(cls, retain); + if ((NULL != slot) && !ownsMethod(slot->owner, isARC)) + { + objc_clear_class_flag(cls, objc_class_flag_fast_arc); + return; + } + slot = objc_get_slot(cls, release); + if ((NULL != slot) && !ownsMethod(slot->owner, isARC)) + { + objc_clear_class_flag(cls, objc_class_flag_fast_arc); + return; + } + slot = objc_get_slot(cls, autorelease); + if ((NULL != slot) && !ownsMethod(slot->owner, isARC)) + { + objc_clear_class_flag(cls, objc_class_flag_fast_arc); + return; + } + objc_set_class_flag(cls, objc_class_flag_fast_arc); +} + +static void collectMethodsForMethodListToSparseArray( + struct objc_method_list *list, + SparseArray *sarray, + BOOL recurse) +{ + if (recurse && (NULL != list->next)) + { + collectMethodsForMethodListToSparseArray(list->next, sarray, YES); + } + for (unsigned i=0 ; i<list->count ; i++) + { + SparseArrayInsert(sarray, list->methods[i].selector->index, + (void*)&list->methods[i]); + } +} + + +#ifdef __OBJC_LOW_MEMORY__ + +struct objc_dtable +{ + struct cache_line + { + uint32_t idx; + uint32_t version; + struct objc_slot *slot; + } cache[8]; + mutex_t lock; + struct objc_slot **slots; + int slot_count; + int slot_size; + Class cls; +}; + +static void update_dtable(dtable_t dtable); + +PRIVATE void init_dispatch_tables () +{ + INIT_LOCK(initialize_lock); +} + +Class class_getSuperclass(Class); + + +static dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +{ + // Don't create a dtable for a class that already has one + if (classHasDtable(class)) { return dtable_for_class(class); } + + LOCK_RUNTIME_FOR_SCOPE(); + + // Make sure that another thread didn't create the dtable while we were + // waiting on the lock. + if (classHasDtable(class)) { return dtable_for_class(class); } + + // Allocate the dtable + dtable_t dtable = calloc(1, sizeof(struct objc_dtable)); + dtable->cls = class; + INIT_LOCK(dtable->lock); + + // Initialise it + update_dtable(dtable); + + return dtable; +} + + +PRIVATE void objc_resize_dtables(uint32_t newSize) +{ + if (1<<dtable_depth > newSize) { return; } + dtable_depth <<= 1; +} + +#define HASH_UID(uid) ((uid >> 2) & 7) + +static struct objc_slot* check_cache(dtable_t dtable, uint32_t uid) +{ + int i = HASH_UID(uid); + volatile struct cache_line *cache = &dtable->cache[i]; + int32_t initial_idx = cache->idx; + + if (initial_idx != uid) + { + return NULL; + } + + struct objc_slot *slot; + int32_t idx; + int32_t version; + do + { + initial_idx = cache->idx; + version = cache->version; + slot = cache->slot; + __sync_synchronize(); + idx = cache->idx; + } while (idx != initial_idx); + + return (idx == uid) && (slot->version == version) ? slot : NULL; +} + +static struct objc_slot *find_slot(uint32_t uid, + struct objc_slot **slots, int slot_count) +{ + if (slot_count == 0) { return NULL; } + int idx = slot_count >> 1; + struct objc_slot *slot = slots[idx]; + if (slot_count == 1) + { + if (slot->selector->index == uid) + { + return slot; + } + return NULL; + } + if (slot->selector->index > uid) + { + return find_slot(uid, slots, idx); + } + if (slot->selector->index < uid) + { + return find_slot(uid, slots+idx, slot_count - idx); + } + if (slot->selector->index == uid) + { + return slot; + } + return NULL; +} + +static int slot_cmp(const void *l, const void *r) +{ + return (*(struct objc_slot**)l)->selector->index + - (*(struct objc_slot**)r)->selector->index; +} + +static void insert_slot(dtable_t dtable, struct objc_slot *slot, uint32_t idx) +{ + if (dtable->slot_size == dtable->slot_count) + { + dtable->slot_size += 16; + dtable->slots = realloc(dtable->slots, dtable->slot_size * + sizeof(struct objc_slot)); + assert(NULL != dtable->slots && "Out of memory!"); + } + dtable->slots[dtable->slot_count++] = slot; +} + +static void add_slot_to_dtable(SEL sel, dtable_t dtable, uint32_t + old_slot_count, struct objc_method *m, Class cls) +{ + uint32_t idx = sel->index; + struct objc_slot *s = find_slot(idx, dtable->slots, old_slot_count); + if (NULL != s) + { + s->method = m->imp; + s->version++; + } + else + { + struct objc_slot *slot = new_slot_for_method_in_class(m, cls); + slot->selector = sel; + insert_slot(dtable, slot, idx); + if (Nil != cls->super_class) + { + slot = objc_dtable_lookup(dtable_for_class(cls->super_class), idx); + if (NULL != slot) + { + slot->version++; + } + } + } +} +static void update_dtable(dtable_t dtable) +{ + Class cls = dtable->cls; + + if (NULL == cls->methods) { return; } + + SparseArray *methods = SparseArrayNewWithDepth(dtable_depth); + collectMethodsForMethodListToSparseArray((void*)cls->methods, methods, YES); + + if (NULL == dtable->slots) + { + dtable->slots = calloc(sizeof(struct objc_slot), 16); + dtable->slot_size = 16; + } + + uint32_t old_slot_count = dtable->slot_count; + struct objc_method *m; + uint32_t idx = 0; + while ((m = SparseArrayNext(methods, &idx))) + { + add_slot_to_dtable(m->selector, dtable, old_slot_count, m, cls); +#ifdef TYPE_DEPENDENT_DISPATCH + add_slot_to_dtable(sel_getUntyped(m->selector), dtable, old_slot_count, m, cls); +#endif + } + mergesort(dtable->slots, dtable->slot_count, sizeof(struct objc_slot*), + slot_cmp); + SparseArrayDestroy(methods); +} + +PRIVATE void objc_update_dtable_for_class(Class cls) +{ + dtable_t dtable = dtable_for_class(cls); + // Be lazy about constructing the slot list - don't do it unless we actually + // need to access it + if ((NULL == dtable) || (NULL == dtable->slots)) { return; } + + LOCK_FOR_SCOPE(&dtable->lock); + + update_dtable(dtable); + +} +PRIVATE void add_method_list_to_class(Class cls, + struct objc_method_list *list) +{ + objc_update_dtable_for_class(cls); +} + +PRIVATE struct objc_slot* objc_dtable_lookup(dtable_t dtable, uint32_t uid) +{ + if (NULL == dtable) { return NULL; } + + struct objc_slot *slot = check_cache(dtable, uid); + + if (NULL != slot) + { + return slot; + } + + LOCK_FOR_SCOPE(&dtable->lock); + if (NULL == dtable->slots) + { + update_dtable(dtable); + } + slot = find_slot(uid, dtable->slots, dtable->slot_count); + if (NULL != slot) + { + int i = HASH_UID(uid); + volatile struct cache_line *cache = &dtable->cache[i]; + // Simplified multiword atomic exchange. First we write a value that + // is an invalid but recognisable UID and then a memory barrier. Then + // we complete the update and set the index pointer if and only if + // there have been no other modifications in the meantime + cache->idx = -uid; + __sync_synchronize(); + cache->version = slot->version; + cache->slot = slot; + __sync_bool_compare_and_swap(&cache->idx, -uid, uid); + return slot; + } + + if (NULL != dtable->cls->super_class) + { + return objc_dtable_lookup(dtable_for_class(dtable->cls->super_class), uid); + } + return NULL; +} +PRIVATE dtable_t objc_copy_dtable_for_class(dtable_t old, Class cls) +{ + dtable_t dtable = calloc(1, sizeof(struct objc_dtable)); + dtable->cls = cls; + INIT_LOCK(dtable->lock); + return dtable; +} + +PRIVATE void free_dtable(dtable_t dtable) +{ + if (NULL != dtable->slots) + { + free(dtable->slots); + } + DESTROY_LOCK(&dtable->lock); + free(dtable); +} + +#else + + +PRIVATE void init_dispatch_tables () +{ + INIT_LOCK(initialize_lock); + uninstalled_dtable = SparseArrayNewWithDepth(dtable_depth); +} + +static BOOL installMethodInDtable(Class class, + Class owner, + SparseArray *dtable, + struct objc_method *method, + BOOL replaceExisting) +{ + ASSERT(uninstalled_dtable != dtable); + uint32_t sel_id = method->selector->index; + struct objc_slot *slot = SparseArrayLookup(dtable, sel_id); + if (NULL != slot) + { + // If this method is the one already installed, pretend to install it again. + if (slot->method == method->imp) { return NO; } + + // If the existing slot is for this class, we can just replace the + // implementation. We don't need to bump the version; this operation + // updates cached slots, it doesn't invalidate them. + if (slot->owner == owner) + { + // Don't replace methods if we're not meant to (if they're from + // later in a method list, for example) + if (!replaceExisting) { return NO; } + slot->method = method->imp; + return YES; + } + + // Check whether the owner of this method is a subclass of the one that + // owns this method. If it is, then we don't want to install this + // method irrespective of other cases, because it has been overridden. + for (Class installedFor = slot->owner ; + Nil != installedFor ; + installedFor = installedFor->super_class) + { + if (installedFor == owner) + { + return NO; + } + } + } + struct objc_slot *oldSlot = slot; + slot = new_slot_for_method_in_class((void*)method, owner); + SparseArrayInsert(dtable, sel_id, slot); + // In TDD mode, we also register the first typed method that we + // encounter as the untyped version. +#ifdef TYPE_DEPENDENT_DISPATCH + SparseArrayInsert(dtable, get_untyped_idx(method->selector), slot); +#endif + // Invalidate the old slot, if there is one. + if (NULL != oldSlot) + { + oldSlot->version++; + } + return YES; +} + +static void installMethodsInClass(Class cls, + Class owner, + SparseArray *methods, + BOOL replaceExisting) +{ + SparseArray *dtable = dtable_for_class(cls); + assert(uninstalled_dtable != dtable); + + uint32_t idx = 0; + struct objc_method *m; + while ((m = SparseArrayNext(methods, &idx))) + { + if (!installMethodInDtable(cls, owner, dtable, m, replaceExisting)) + { + // Remove this method from the list, if it wasn't actually installed + SparseArrayInsert(methods, idx, 0); + } + } +} + +static void mergeMethodsFromSuperclass(Class super, Class cls, SparseArray *methods) +{ + for (struct objc_class *subclass=cls->subclass_list ; + Nil != subclass ; subclass = subclass->sibling_class) + { + // Don't bother updating dtables for subclasses that haven't been + // initialized yet + if (!classHasDtable(subclass)) { continue; } + + // Create a new (copy-on-write) array to pass down to children + SparseArray *newMethods = SparseArrayCopy(methods); + // Install all of these methods except ones that are overridden in the + // subclass. All of the methods that we are updating were added in a + // superclass, so we don't replace versions registered to the subclass. + installMethodsInClass(subclass, super, newMethods, YES); + // Recursively add the methods to the subclass's subclasses. + mergeMethodsFromSuperclass(super, subclass, newMethods); + SparseArrayDestroy(newMethods); + } +} + +Class class_getSuperclass(Class); + +PRIVATE void objc_update_dtable_for_class(Class cls) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + + SparseArray *methods = SparseArrayNewWithDepth(dtable_depth); + collectMethodsForMethodListToSparseArray((void*)cls->methods, methods, YES); + installMethodsInClass(cls, cls, methods, YES); + // Methods now contains only the new methods for this class. + mergeMethodsFromSuperclass(cls, cls, methods); + SparseArrayDestroy(methods); + checkARCAccessors(cls); +} + +PRIVATE void add_method_list_to_class(Class cls, + struct objc_method_list *list) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + + SparseArray *methods = SparseArrayNewWithDepth(dtable_depth); + collectMethodsForMethodListToSparseArray(list, methods, NO); + installMethodsInClass(cls, cls, methods, YES); + // Methods now contains only the new methods for this class. + mergeMethodsFromSuperclass(cls, cls, methods); + SparseArrayDestroy(methods); + checkARCAccessors(cls); +} + +static dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +{ + // Don't create a dtable for a class that already has one + if (classHasDtable(class)) { return dtable_for_class(class); } + + LOCK_RUNTIME_FOR_SCOPE(); + + // Make sure that another thread didn't create the dtable while we were + // waiting on the lock. + if (classHasDtable(class)) { return dtable_for_class(class); } + + Class super = class_getSuperclass(class); + dtable_t dtable; + + + if (Nil == super) + { + dtable = SparseArrayNewWithDepth(dtable_depth); + } + else + { + dtable_t super_dtable = dtable_for_class(super); + if (super_dtable == uninstalled_dtable) + { + if (super->isa == class) + { + super_dtable = root_dtable; + } + else + { + abort(); + } + } + dtable = SparseArrayCopy(super_dtable); + } + + // When constructing the initial dtable for a class, we iterate along the + // method list in forward-traversal order. The first method that we + // encounter is always the one that we want to keep, so we instruct + // installMethodInDtable() not to replace methods that are already + // associated with this class. + struct objc_method_list *list = (void*)class->methods; + + while (NULL != list) + { + for (unsigned i=0 ; i<list->count ; i++) + { + installMethodInDtable(class, class, dtable, &list->methods[i], NO); + } + list = list->next; + } + + return dtable; +} + + +Class class_table_next(void **e); + +PRIVATE void objc_resize_dtables(uint32_t newSize) +{ + // If dtables already have enough space to store all registered selectors, do nothing + if (1<<dtable_depth > newSize) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + + dtable_depth <<= 1; + + uint32_t oldMask = uninstalled_dtable->mask; + + SparseArrayExpandingArray(uninstalled_dtable); + // Resize all existing dtables + void *e = NULL; + struct objc_class *next; + while ((next = class_table_next(&e))) + { + if (next->dtable != (void*)uninstalled_dtable && + NULL != next->dtable && + ((SparseArray*)next->dtable)->mask == oldMask) + { + SparseArrayExpandingArray((void*)next->dtable); + } + } +} + +PRIVATE dtable_t objc_copy_dtable_for_class(dtable_t old, Class cls) +{ + return SparseArrayCopy(old); +} + +PRIVATE void free_dtable(dtable_t dtable) +{ + SparseArrayDestroy(dtable); +} + +#endif // __OBJC_LOW_MEMORY__ + +LEGACY void update_dispatch_table_for_class(Class cls) +{ + static BOOL warned = NO; + if (!warned) + { + fprintf(stderr, + "Warning: Calling deprecated private ObjC runtime function %s\n", __func__); + warned = YES; + } + objc_update_dtable_for_class(cls); +} + +void objc_resolve_class(Class); + +__attribute__((unused)) static void objc_release_object_lock(id *x) +{ + objc_sync_exit(*x); +} +/** + * Macro that is equivalent to @synchronize, for use in C code. + */ +#define LOCK_OBJECT_FOR_SCOPE(obj) \ + __attribute__((cleanup(objc_release_object_lock)))\ + __attribute__((unused)) id lock_object_pointer = obj;\ + objc_sync_enter(obj); + +/** + * Remove a buffer from an entry in the initializing dtables list. This is + * called as a cleanup to ensure that it runs even if +initialize throws an + * exception. + */ +static void remove_dtable(InitializingDtable* meta_buffer) +{ + LOCK(&initialize_lock); + InitializingDtable *buffer = meta_buffer->next; + // Install the dtable: + meta_buffer->class->dtable = meta_buffer->dtable; + buffer->class->dtable = buffer->dtable; + // Remove the look-aside buffer entry. + if (temporary_dtables == meta_buffer) + { + temporary_dtables = buffer->next; + } + else + { + InitializingDtable *prev = temporary_dtables; + while (prev->next->class != meta_buffer->class) + { + prev = prev->next; + } + prev->next = buffer->next; + } + UNLOCK(&initialize_lock); +} + +/** + * Send a +initialize message to the receiver, if required. + */ +PRIVATE void objc_send_initialize(id object) +{ + Class class = classForObject(object); + // If the first message is sent to an instance (weird, but possible and + // likely for things like NSConstantString, make sure +initialize goes to + // the class not the metaclass. + if (objc_test_class_flag(class, objc_class_flag_meta)) + { + class = (Class)object; + } + Class meta = class->isa; + + + // Make sure that the class is resolved. + objc_resolve_class(class); + + // Make sure that the superclass is initialized first. + if (Nil != class->super_class) + { + objc_send_initialize((id)class->super_class); + } + + LOCK(&initialize_lock); + + // Superclass +initialize might possibly send a message to this class, in + // which case this method would be called again. See NSObject and + // NSAutoreleasePool +initialize interaction in GNUstep. + if (objc_test_class_flag(class, objc_class_flag_initialized)) + { + UNLOCK(&initialize_lock); + // We know that initialization has started because the flag is set. + // Check that it's finished by grabbing the class lock. This will be + // released once the class has been fully initialized + objc_sync_enter((id)meta); + objc_sync_exit((id)meta); + assert(dtable_for_class(class) != uninstalled_dtable); + return; + } + + LOCK_OBJECT_FOR_SCOPE((id)meta); + + // Set the initialized flag on both this class and its metaclass, to make + // sure that +initialize is only ever sent once. + objc_set_class_flag(class, objc_class_flag_initialized); + objc_set_class_flag(meta, objc_class_flag_initialized); + + dtable_t class_dtable = create_dtable_for_class(class, uninstalled_dtable); + dtable_t dtable = create_dtable_for_class(meta, class_dtable); + + static SEL initializeSel = 0; + if (0 == initializeSel) + { + initializeSel = sel_registerName("initialize"); + } + + struct objc_slot *initializeSlot = + objc_dtable_lookup(dtable, initializeSel->index); + + // If there's no initialize method, then don't bother installing and + // removing the initialize dtable, just install both dtables correctly now + if (0 == initializeSlot) + { + meta->dtable = dtable; + class->dtable = class_dtable; + checkARCAccessors(class); + UNLOCK(&initialize_lock); + return; + } + + + + // Create an entry in the dtable look-aside buffer for this. When sending + // a message to this class in future, the lookup function will check this + // buffer if the receiver's dtable is not installed, and block if + // attempting to send a message to this class. + InitializingDtable buffer = { class, class_dtable, temporary_dtables }; + __attribute__((cleanup(remove_dtable))) + InitializingDtable meta_buffer = { meta, dtable, &buffer }; + temporary_dtables = &meta_buffer; + UNLOCK(&initialize_lock); + + checkARCAccessors(class); + + // Store the buffer in the temporary dtables list. Note that it is safe to + // insert it into a global list, even though it's a temporary variable, + // because we will clean it up after this function. + initializeSlot->method((id)class, initializeSel); +} diff --git a/third_party/libobjc/dtable.h b/third_party/libobjc/dtable.h new file mode 100644 index 0000000000000000000000000000000000000000..95eae20888572f815216831d6093fef0d5c3b754 --- /dev/null +++ b/third_party/libobjc/dtable.h @@ -0,0 +1,128 @@ +#include "lock.h" +#include "class.h" +#include "sarray2.h" +#include "objc/slot.h" +#include "visibility.h" +#include <stdint.h> +#include <stdio.h> + +#ifdef __OBJC_LOW_MEMORY__ +typedef struct objc_dtable* dtable_t; +struct objc_slot* objc_dtable_lookup(dtable_t dtable, uint32_t uid); +#else +typedef SparseArray* dtable_t; +# define objc_dtable_lookup SparseArrayLookup +#endif + +/** + * Pointer to the sparse array representing the pretend (uninstalled) dtable. + */ +PRIVATE extern dtable_t uninstalled_dtable; +/** + * Structure for maintaining a linked list of temporary dtables. When sending + * an +initialize message to a class, we create a temporary dtables and store + * it in a linked list. This is then used when sending other messages to + * instances of classes in the middle of initialisation. + */ +typedef struct _InitializingDtable +{ + /** The class that owns the dtable. */ + Class class; + /** The dtable for this class. */ + dtable_t dtable; + /** The next uninstalled dtable in the list. */ + struct _InitializingDtable *next; +} InitializingDtable; + +/** Head of the list of temporary dtables. Protected by initialize_lock. */ +extern InitializingDtable *temporary_dtables; +extern mutex_t initialize_lock; + +/** + * Returns whether a class has an installed dtable. + */ +static inline int classHasInstalledDtable(struct objc_class *cls) +{ + return (cls->dtable != uninstalled_dtable); +} + +int objc_sync_enter(id object); +int objc_sync_exit(id object); +/** + * Returns the dtable for a given class. If we are currently in an +initialize + * method then this will block if called from a thread other than the one + * running the +initialize method. + */ +static inline dtable_t dtable_for_class(Class cls) +{ + if (classHasInstalledDtable(cls)) + { + return cls->dtable; + } + + dtable_t dtable = uninstalled_dtable; + + { + LOCK_FOR_SCOPE(&initialize_lock); + if (classHasInstalledDtable(cls)) + { + return cls->dtable; + } + /* This is a linear search, and so, in theory, could be very slow. It + * is O(n) where n is the number of +initialize methods on the stack. + * In practice, this is a very small number. Profiling with GNUstep + * showed that this peaks at 8. */ + InitializingDtable *buffer = temporary_dtables; + while (NULL != buffer) + { + if (buffer->class == cls) + { + dtable = buffer->dtable; + break; + } + buffer = buffer->next; + } + } + + if (dtable != uninstalled_dtable) + { + // Make sure that we block if +initialize is still running. We do this + // after we've released the initialize lock, so that the real dtable + // can be installed. This acquires / releases a recursive mutex, so if + // this mutex is already held by this thread then this will proceed + // immediately. If it's held by another thread (i.e. the one running + // +initialize) then we block here until it's run. We don't need to do + // this if the dtable is the uninstalled dtable, because that means + // +initialize has not yet been sent, so we can wait until something + // triggers it before needing any synchronisation. + objc_sync_enter((id)cls); + objc_sync_exit((id)cls); + } + return dtable; +} + +/** + * Returns whether a class has had a dtable created. The dtable may be + * installed, or stored in the look-aside buffer. + */ +static inline int classHasDtable(struct objc_class *cls) +{ + return (dtable_for_class(cls) != uninstalled_dtable); +} + +/** + * Updates the dtable for a class and its subclasses. Must be called after + * modifying a class's method list. + */ +void objc_update_dtable_for_class(Class); +/** + * Adds a single method list to a class. This is used when loading categories, + * and is faster than completely rebuilding the dtable. + */ +void add_method_list_to_class(Class cls, + struct objc_method_list *list); + +/** + * Destroys a dtable. + */ +void free_dtable(dtable_t dtable); diff --git a/third_party/libobjc/dwarf_eh.h b/third_party/libobjc/dwarf_eh.h new file mode 100644 index 0000000000000000000000000000000000000000..6dbe1e6d83140fcef085aa792f93e06485233e0e --- /dev/null +++ b/third_party/libobjc/dwarf_eh.h @@ -0,0 +1,323 @@ +/** + * This file is Copyright PathScale 2010. Permission granted to distribute + * according to the terms of the MIT license (see COPYING.MIT) + */ +#include <assert.h> +#include <stdint.h> + +// _GNU_SOURCE must be defined for unwind.h to expose some of the functions +// that we want. If it isn't, then we define it and undefine it to make sure +// that it doesn't impact the rest of the program. +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +# include "unwind.h" +# undef _GNU_SOURCE +#else +# include "unwind.h" +#endif + +/** + * Type used to store pointers to values computed by DWARF expressions. + */ +typedef unsigned char *dw_eh_ptr_t; +// Flag indicating a signed quantity +#define DW_EH_PE_signed 0x08 +/// DWARF data encoding types +enum dwarf_data_encoding +{ + // Unsigned, little-endian, base 128-encoded (variable length) + DW_EH_PE_uleb128 = 0x01, + // uint16 + DW_EH_PE_udata2 = 0x02, + // uint32 + DW_EH_PE_udata4 = 0x03, + // uint64 + DW_EH_PE_udata8 = 0x04, + // Signed versions of the above: + DW_EH_PE_sleb128 = DW_EH_PE_uleb128 | DW_EH_PE_signed, + DW_EH_PE_sdata2 = DW_EH_PE_udata2 | DW_EH_PE_signed, + DW_EH_PE_sdata4 = DW_EH_PE_udata4 | DW_EH_PE_signed, + DW_EH_PE_sdata8 = DW_EH_PE_udata8 | DW_EH_PE_signed +}; + +static inline enum dwarf_data_encoding get_encoding(unsigned char x) +{ + return (enum dwarf_data_encoding)(x & 0xf); +} + +enum dwarf_data_relative +{ + // Value is omitted + DW_EH_PE_omit = 0xff, + // Absolute pointer value + DW_EH_PE_absptr = 0x00, + // Value relative to program counter + DW_EH_PE_pcrel = 0x10, + // Value relative to the text segment + DW_EH_PE_textrel = 0x20, + // Value relative to the data segment + DW_EH_PE_datarel = 0x30, + // Value relative to the start of the function + DW_EH_PE_funcrel = 0x40, + // Aligned pointer (Not supported yet - are they actually used?) + DW_EH_PE_aligned = 0x50, + // Pointer points to address of real value + DW_EH_PE_indirect = 0x80 +}; +static inline enum dwarf_data_relative get_base(unsigned char x) +{ + return (enum dwarf_data_relative)(x & 0x70); +} +static int is_indirect(unsigned char x) +{ + return (x & DW_EH_PE_indirect); +} + +static inline int dwarf_size_of_fixed_size_field(unsigned char type) +{ + // Low three bits indicate size... + switch (type & 7) + { + case DW_EH_PE_udata2: return 2; + case DW_EH_PE_udata4: return 4; + case DW_EH_PE_udata8: return 8; + case DW_EH_PE_absptr: return sizeof(void*); + } + abort(); +} + +/** + * Read an unsigned, little-endian, base-128, DWARF value. Updates *data to + * point to the end of the value. + */ +static uint64_t read_leb128(unsigned char** data, int *b) +{ + uint64_t uleb = 0; + unsigned int bit = 0; + unsigned char digit = 0; + // We have to read at least one octet, and keep reading until we get to one + // with the high bit unset + do + { + // This check is a bit too strict - we should also check the highest + // bit of the digit. + assert(bit < sizeof(uint64_t) * 8); + // Get the base 128 digit + digit = (**data) & 0x7f; + // Add it to the current value + uleb += digit << bit; + // Increase the shift value + bit += 7; + // Proceed to the next octet + (*data)++; + // Terminate when we reach a value that does not have the high bit set + // (i.e. which was not modified when we mask it with 0x7f) + } while ((*(*data - 1)) != digit); + *b = bit; + + return uleb; +} + +static int64_t read_uleb128(unsigned char** data) +{ + int b; + return read_leb128(data, &b); +} + + +static int64_t read_sleb128(unsigned char** data) +{ + int bits; + // Read as if it's signed + uint64_t uleb = read_leb128(data, &bits); + // If the most significant bit read is 1, then we need to sign extend it + if (uleb >> (bits-1) == 1) + { + // Sign extend by setting all bits in front of it to 1 + uleb |= ((int64_t)-1) << bits; + } + return (int64_t)uleb; +} + +static uint64_t read_value(char encoding, unsigned char **data) +{ + enum dwarf_data_encoding type = get_encoding(encoding); + uint64_t v; + switch ((int)type) + { + // Read fixed-length types +#define READ(dwarf, type) \ + case dwarf:\ + v = (uint64_t)(*(type*)(*data));\ + *data += sizeof(type);\ + break; + READ(DW_EH_PE_udata2, uint16_t) + READ(DW_EH_PE_udata4, uint32_t) + READ(DW_EH_PE_udata8, uint64_t) + READ(DW_EH_PE_sdata2, int16_t) + READ(DW_EH_PE_sdata4, int32_t) + READ(DW_EH_PE_sdata8, int64_t) + case DW_EH_PE_absptr: + v = (uint64_t)(*(intptr_t*)(*data)); + *data += sizeof(intptr_t); + break; + //READ(DW_EH_PE_absptr, intptr_t) +#undef READ + case DW_EH_PE_sleb128: + v = read_sleb128(data); + break; + case DW_EH_PE_uleb128: + v = read_uleb128(data); + break; + default: abort(); + } + + return v; +} + +static uint64_t resolve_indirect_value(struct _Unwind_Context *c, unsigned char encoding, int64_t v, dw_eh_ptr_t start) +{ + switch (get_base(encoding)) + { + case DW_EH_PE_pcrel: + v += (uint64_t)(uintptr_t)start; + break; + case DW_EH_PE_textrel: + v += (uint64_t)(uintptr_t)_Unwind_GetTextRelBase(c); + break; + case DW_EH_PE_datarel: + v += (uint64_t)(uintptr_t)_Unwind_GetDataRelBase(c); + break; + case DW_EH_PE_funcrel: + v += (uint64_t)(uintptr_t)_Unwind_GetRegionStart(c); + default: + break; + } + // If this is an indirect value, then it is really the address of the real + // value + // TODO: Check whether this should really always be a pointer - it seems to + // be a GCC extensions, so not properly documented... + if (is_indirect(encoding)) + { + v = (uint64_t)(uintptr_t)*(void**)(uintptr_t)v; + } + return v; +} + + +static inline void read_value_with_encoding(struct _Unwind_Context *context, + dw_eh_ptr_t *data, + uint64_t *out) +{ + dw_eh_ptr_t start = *data; + unsigned char encoding = *((*data)++); + // If this value is omitted, skip it and don't touch the output value + if (encoding == DW_EH_PE_omit) { return; } + + *out = read_value(encoding, data); + *out = resolve_indirect_value(context, encoding, *out, start); +} + + +struct dwarf_eh_lsda +{ + dw_eh_ptr_t region_start; + dw_eh_ptr_t landing_pads; + dw_eh_ptr_t type_table; + unsigned char type_table_encoding; + dw_eh_ptr_t call_site_table; + dw_eh_ptr_t action_table; + unsigned char callsite_encoding; +}; + +static inline struct dwarf_eh_lsda parse_lsda(struct _Unwind_Context *context, unsigned char *data) +{ + struct dwarf_eh_lsda lsda; + + lsda.region_start = (dw_eh_ptr_t)(uintptr_t)_Unwind_GetRegionStart(context); + + // If the landing pads are relative to anything other than the start of + // this region, find out where. This is @LPStart in the spec, although the + // encoding that GCC uses does not quite match the spec. + uint64_t v = (uint64_t)(uintptr_t)lsda.region_start; + read_value_with_encoding(context, &data, &v); + lsda.landing_pads = (dw_eh_ptr_t)(uintptr_t)v; + + // If there is a type table, find out where it is. This is @TTBase in the + // spec. Note: we find whether there is a type table pointer by checking + // whether the leading byte is DW_EH_PE_omit (0xff), which is not what the + // spec says, but does seem to be how G++ indicates this. + lsda.type_table = 0; + lsda.type_table_encoding = *data++; + if (lsda.type_table_encoding != DW_EH_PE_omit) + { + v = read_uleb128(&data); + dw_eh_ptr_t type_table = data; + type_table += v; + lsda.type_table = type_table; + //lsda.type_table = (uintptr_t*)(data + v); + } + + lsda.callsite_encoding = (enum dwarf_data_encoding)(*(data++)); + + // Action table is immediately after the call site table + lsda.action_table = data; + uintptr_t callsite_size = (uintptr_t)read_uleb128(&data); + lsda.action_table = data + callsite_size; + // Call site table is immediately after the header + lsda.call_site_table = (dw_eh_ptr_t)data; + + + return lsda; +} + +struct dwarf_eh_action +{ + dw_eh_ptr_t landing_pad; + dw_eh_ptr_t action_record; +}; + +/** + * Look up the landing pad that corresponds to the current invoke. + */ +__attribute__((unused)) +static struct dwarf_eh_action + dwarf_eh_find_callsite(struct _Unwind_Context *context, struct dwarf_eh_lsda *lsda) +{ + struct dwarf_eh_action result = { 0, 0}; + uint64_t ip = _Unwind_GetIP(context) - _Unwind_GetRegionStart(context); + unsigned char *callsite_table = (unsigned char*)lsda->call_site_table; + while (callsite_table <= lsda->action_table) + { + // Once again, the layout deviates from the spec. + uint64_t call_site_start, call_site_size, landing_pad, action; + call_site_start = read_value(lsda->callsite_encoding, &callsite_table); + call_site_size = read_value(lsda->callsite_encoding, &callsite_table); + + // Call site entries are started + if (call_site_start > ip) { break; } + + landing_pad = read_value(lsda->callsite_encoding, &callsite_table); + action = read_uleb128(&callsite_table); + + if (call_site_start <= ip && ip <= call_site_start + call_site_size) + { + if (action) + { + // Action records are 1-biased so both no-record and zeroth + // record can be stored. + result.action_record = lsda->action_table + action - 1; + } + // No landing pad means keep unwinding. + if (landing_pad) + { + // Landing pad is the offset from the value in the header + result.landing_pad = lsda->landing_pads + landing_pad; + } + break; + } + } + return result; +} + +#define EXCEPTION_CLASS(a,b,c,d,e,f,g,h) (((uint64_t)a << 56) + ((uint64_t)b << 48) + ((uint64_t)c << 40) + ((uint64_t)d << 32) + ((uint64_t)e << 24) + ((uint64_t)f << 16) + ((uint64_t)g << 8) + ((uint64_t)h)) diff --git a/third_party/libobjc/eh_personality.c b/third_party/libobjc/eh_personality.c new file mode 100644 index 0000000000000000000000000000000000000000..e3e4ead29c5cbdf8e7f08aa649539b2d30c4c9ae --- /dev/null +++ b/third_party/libobjc/eh_personality.c @@ -0,0 +1,449 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "dwarf_eh.h" +#include "objc/runtime.h" +#include "objc/hooks.h" +#include "class.h" +#include "objcxx_eh.h" + +#define fprintf(...) + +/** + * Class of exceptions to distinguish between this and other exception types. + */ +static const uint64_t objc_exception_class = EXCEPTION_CLASS('G','N','U','C','O','B','J','C'); +static const uint64_t cxx_exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); + +/** + * Structure used as a header on thrown exceptions. + */ +struct objc_exception +{ + /** The selector value to be returned when installing the catch handler. + * Used at the call site to determine which catch() block should execute. + * This is found in phase 1 of unwinding then installed in phase 2.*/ + int handlerSwitchValue; + /** The cached landing pad for the catch handler.*/ + void *landingPad; + + /** The language-agnostic part of the exception header. */ + struct _Unwind_Exception unwindHeader; + /** Thrown object. This is after the unwind header so that the C++ + * exception handler can catch this as a foreign exception. */ + id object; + /** C++ exception structure. Used for mixed exceptions. When we are in + * Objective-C++ code, we create this structure for passing to the C++ + * exception personality function. It will then handle installing + * exceptions for us. */ + struct _Unwind_Exception *cxx_exception; +}; + +typedef enum +{ + handler_none, + handler_cleanup, + handler_catchall_id, + handler_catchall, + handler_class +} handler_type; + +/** + * Saves the result of the landing pad that we have found. For ARM, this is + * stored in the generic unwind structure, while on other platforms it is + * stored in the Objective-C exception. + */ +static void saveLandingPad(struct _Unwind_Context *context, + struct _Unwind_Exception *ucb, + struct objc_exception *ex, + int selector, + dw_eh_ptr_t landingPad) +{ +#ifdef __arm__ + // On ARM, we store the saved exception in the generic part of the structure + ucb->barrier_cache.sp = _Unwind_GetGR(context, 13); + ucb->barrier_cache.bitpattern[1] = (uint32_t)selector; + ucb->barrier_cache.bitpattern[3] = (uint32_t)landingPad; +#else + // Cache the results for the phase 2 unwind, if we found a handler + // and this is not a foreign exception. We can't cache foreign exceptions + // because we don't know their structure (although we could cache C++ + // exceptions...) + if (ex) + { + ex->handlerSwitchValue = selector; + ex->landingPad = landingPad; + } +#endif +} + +/** + * Loads the saved landing pad. Returns 1 on success, 0 on failure. + */ +static int loadLandingPad(struct _Unwind_Context *context, + struct _Unwind_Exception *ucb, + struct objc_exception *ex, + unsigned long *selector, + dw_eh_ptr_t *landingPad) +{ +#ifdef __arm__ + *selector = ucb->barrier_cache.bitpattern[1]; + *landingPad = (dw_eh_ptr_t)ucb->barrier_cache.bitpattern[3]; + return 1; +#else + if (ex) + { + *selector = ex->handlerSwitchValue; + *landingPad = ex->landingPad; + return 0; + } + return 0; +#endif +} + +static inline _Unwind_Reason_Code continueUnwinding(struct _Unwind_Exception *ex, + struct _Unwind_Context *context) +{ +#ifdef __arm__ + if (__gnu_unwind_frame(ex, context) != _URC_OK) { return _URC_FAILURE; } +#endif + return _URC_CONTINUE_UNWIND; +} + +static void cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *e) +{ + /* + if (header->exceptionDestructor) + header->exceptionDestructor (e + 1); + + free((struct objc_exception*) ((char*)e - offsetof(struct objc_exception, + unwindHeader))); + */ +} +/** + * Throws an Objective-C exception. This function is, unfortunately, used for + * rethrowing caught exceptions too, even in @finally() blocks. Unfortunately, + * this means that we have some problems if the exception is boxed. + */ +void objc_exception_throw(id object) +{ + + SEL rethrow_sel = sel_registerName("rethrow"); + if ((nil != object) && + (class_respondsToSelector(classForObject(object), rethrow_sel))) + { + fprintf(stderr, "Rethrowing\n"); + IMP rethrow = objc_msg_lookup(object, rethrow_sel); + rethrow(object, rethrow_sel); + // Should not be reached! If it is, then the rethrow method actually + // didn't, so we throw it normally. + } + + fprintf(stderr, "Throwing %p\n", object); + + struct objc_exception *ex = calloc(1, sizeof(struct objc_exception)); + + ex->unwindHeader.exception_class = objc_exception_class; + ex->unwindHeader.exception_cleanup = cleanup; + + ex->object = object; + + _Unwind_Reason_Code err = _Unwind_RaiseException(&ex->unwindHeader); + free(ex); + if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception) + { + _objc_unexpected_exception(object); + } + fprintf(stderr, "Throw returned %d\n",(int) err); + abort(); +} + +static Class get_type_table_entry(struct _Unwind_Context *context, + struct dwarf_eh_lsda *lsda, + int filter) +{ + dw_eh_ptr_t record = lsda->type_table - + dwarf_size_of_fixed_size_field(lsda->type_table_encoding)*filter; + dw_eh_ptr_t start = record; + int64_t offset = read_value(lsda->type_table_encoding, &record); + + if (0 == offset) { return Nil; } + + // ...so we need to resolve it + char *class_name = (char*)(intptr_t)resolve_indirect_value(context, + lsda->type_table_encoding, offset, start); + + if (0 == class_name) { return Nil; } + + fprintf(stderr, "Class name: %s\n", class_name); + + if (strcmp("@id", class_name) == 0) { return (Class)1; } + + return (Class)objc_getClass(class_name); +} + +static BOOL isKindOfClass(Class thrown, Class type) +{ + do + { + if (thrown == type) + { + return YES; + } + thrown = class_getSuperclass(thrown); + } while (Nil != thrown); + + return NO; +} + + +static handler_type check_action_record(struct _Unwind_Context *context, + BOOL foreignException, + struct dwarf_eh_lsda *lsda, + dw_eh_ptr_t action_record, + Class thrown_class, + unsigned long *selector) +{ + //if (!action_record) { return handler_cleanup; } + while (action_record) + { + int filter = read_sleb128(&action_record); + dw_eh_ptr_t action_record_offset_base = action_record; + int displacement = read_sleb128(&action_record); + *selector = filter; + fprintf(stderr, "Filter: %d\n", filter); + if (filter > 0) + { + Class type = get_type_table_entry(context, lsda, filter); + fprintf(stderr, "%p type: %d\n", type, !foreignException); + // Catchall + if (Nil == type) + { + return handler_catchall; + } + // We treat id catches as catchalls when an object is thrown and as + // nothing when a foreign exception is thrown + else if ((Class)1 == type) + { + fprintf(stderr, "Found id catch\n"); + if (!foreignException) + { + return handler_catchall_id; + } + } + else if (!foreignException && isKindOfClass(thrown_class, type)) + { + fprintf(stderr, "found handler for %s\n", type->name); + return handler_class; + } + else if (thrown_class == type) + { + return handler_class; + } + } + else if (filter == 0) + { + fprintf(stderr, "0 filter\n"); + // Cleanup? I think the GNU ABI doesn't actually use this, but it + // would be a good way of indicating a non-id catchall... + return handler_cleanup; + } + else + { + fprintf(stderr, "Filter value: %d\n" + "Your compiler and I disagree on the correct layout of EH data.\n", + filter); + abort(); + } + *selector = 0; + action_record = displacement ? + action_record_offset_base + displacement : 0; + } + return handler_none; +} + +/** + * The Objective-C exception personality function. + */ +BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0) + fprintf(stderr, "Personality function called\n"); + + // This personality function is for version 1 of the ABI. If you use it + // with a future version of the ABI, it won't know what to do, so it + // reports a fatal error and give up before it breaks anything. + if (1 != version) + { + return _URC_FATAL_PHASE1_ERROR; + } + struct objc_exception *ex = 0; +#ifndef fprintf + char *cls = (char*)&exceptionClass; +#endif + fprintf(stderr, "Class: %c%c%c%c%c%c%c%c\n", cls[7], cls[6], cls[5], cls[4], cls[3], cls[2], cls[1], cls[0]); + + // Check if this is a foreign exception. If it is a C++ exception, then we + // have to box it. If it's something else, like a LanguageKit exception + // then we ignore it (for now) + BOOL foreignException = exceptionClass != objc_exception_class; + // Is this a C++ exception containing an Objective-C++ object? + BOOL objcxxException = NO; + // The object to return + void *object = NULL; + +#ifdef NO_OBJCXX + if (exceptionClass == cxx_exception_class) + { + id obj = objc_object_for_cxx_exception(exceptionObject); + if (obj != (id)-1) + { + object = obj; + fprintf(stderr, "ObjC++ object exception %p\n", object); + objcxxException = YES; + // This is a foreign exception, buy for the purposes of exception + // matching, we pretend that it isn't. + foreignException = NO; + } + } +#endif + + Class thrown_class = Nil; + + if (objcxxException) + { + thrown_class = (object == 0) ? Nil : classForObject((id)object); + } + // If it's not a foreign exception, then we know the layout of the + // language-specific exception stuff. + else if (!foreignException) + { + ex = (struct objc_exception*) ((char*)exceptionObject - + offsetof(struct objc_exception, unwindHeader)); + + thrown_class = classForObject(ex->object); + } + else if (_objc_class_for_boxing_foreign_exception) + { + thrown_class = _objc_class_for_boxing_foreign_exception(exceptionClass); + fprintf(stderr, "Foreign class: %p\n", thrown_class); + } + unsigned char *lsda_addr = (void*)_Unwind_GetLanguageSpecificData(context); + fprintf(stderr, "LSDA: %p\n", lsda_addr); + + // No LSDA implies no landing pads - try the next frame + if (0 == lsda_addr) { return _URC_CONTINUE_UNWIND; } + + // These two variables define how the exception will be handled. + struct dwarf_eh_action action = {0}; + unsigned long selector = 0; + + if (actions & _UA_SEARCH_PHASE) + { + fprintf(stderr, "Search phase...\n"); + struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); + action = dwarf_eh_find_callsite(context, &lsda); + handler_type handler = check_action_record(context, foreignException, + &lsda, action.action_record, thrown_class, &selector); + // If there's no action record, we've only found a cleanup, so keep + // searching for something real + if (handler == handler_class || + ((handler == handler_catchall_id) && !foreignException) || + (handler == handler_catchall)) + { + saveLandingPad(context, exceptionObject, ex, selector, action.landing_pad); + fprintf(stderr, "Found handler! %d\n", handler); + return _URC_HANDLER_FOUND; + } + return _URC_CONTINUE_UNWIND; + } + fprintf(stderr, "Phase 2: Fight!\n"); + + // TODO: If this is a C++ exception, we can cache the lookup and cheat a + // bit + if (!(actions & _UA_HANDLER_FRAME)) + { + struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); + action = dwarf_eh_find_callsite(context, &lsda); + // If there's no cleanup here, continue unwinding. + if (0 == action.landing_pad) + { + return _URC_CONTINUE_UNWIND; + } + handler_type handler = check_action_record(context, foreignException, + &lsda, action.action_record, thrown_class, &selector); + fprintf(stderr, "handler! %d %d\n", (int)handler, (int)selector); + // If this is not a cleanup, ignore it and keep unwinding. + //if (check_action_record(context, foreignException, &lsda, + //action.action_record, thrown_class, &selector) != handler_cleanup) + if (handler != handler_cleanup) + { + fprintf(stderr, "Ignoring handler! %d\n",handler); + return _URC_CONTINUE_UNWIND; + } + fprintf(stderr, "Installing cleanup...\n"); + // If there is a cleanup, we need to return the exception structure + // (not the object) to the calling frame. The exception object + object = exceptionObject; + //selector = 0; + } + else if (foreignException || objcxxException) + { + struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); + action = dwarf_eh_find_callsite(context, &lsda); + check_action_record(context, foreignException, &lsda, + action.action_record, thrown_class, &selector); + // If it's a foreign exception, then box it. If it's an Objective-C++ + // exception, then we need to delete the exception object. + if (foreignException) + { + fprintf(stderr, "Doing the foreign exception thing...\n"); + //[thrown_class exceptionWithForeignException: exceptionObject]; + SEL box_sel = sel_registerName("exceptionWithForeignException:"); + IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel); + object = boxfunction((id)thrown_class, box_sel, exceptionObject); + fprintf(stderr, "Boxed as %p\n", object); + } + else // ObjCXX exception + { + _Unwind_DeleteException(exceptionObject); + } + } + else + { + // Restore the saved info if we saved some last time. + loadLandingPad(context, exceptionObject, ex, &selector, &action.landing_pad); + object = ex->object; + free(ex); + } + + _Unwind_SetIP(context, (unsigned long)action.landing_pad); + _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), + (unsigned long)object); + _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), selector); + + return _URC_INSTALL_CONTEXT; +} + +// FIXME! +#ifndef __arm__ +BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0) + if (exceptionClass == objc_exception_class) + { + struct objc_exception *ex = (struct objc_exception*) + ((char*)exceptionObject - offsetof(struct objc_exception, + unwindHeader)); + if (0 == ex->cxx_exception) + { + id *newEx = __cxa_allocate_exception(sizeof(id)); + *newEx = ex->object; + ex->cxx_exception = objc_init_cxx_exception(newEx); + ex->cxx_exception->exception_class = cxx_exception_class; + ex->cxx_exception->exception_cleanup = cleanup; + ex->cxx_exception->private_1 = exceptionObject->private_1; + ex->cxx_exception->private_2 = exceptionObject->private_2; + } + exceptionObject = ex->cxx_exception; + exceptionClass = cxx_exception_class; + } + return CALL_PERSONALITY_FUNCTION(__gxx_personality_v0); +} +#endif diff --git a/third_party/libobjc/encoding2.c b/third_party/libobjc/encoding2.c new file mode 100644 index 0000000000000000000000000000000000000000..5c9b4edcf1a5380a6c73838f9274845ae60c90a1 --- /dev/null +++ b/third_party/libobjc/encoding2.c @@ -0,0 +1,605 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include "objc/runtime.h" +#include "method_list.h" +#include "visibility.h" + +size_t objc_alignof_type (const char *type); + +// It would be so nice if this works, but in fact it returns nonsense: +//#define alignof(x) __alignof__(x) +// +#define alignof(type) __builtin_offsetof(struct { const char c; type member; }, member) + +const char *objc_skip_type_qualifiers (const char *type) +{ + static const char *type_qualifiers = "rnNoORV"; + while('\0' != *type && strchr(type_qualifiers, *type)) + { + type++; + } + return type; +} + +static const char *sizeof_type(const char *type, size_t *size); + +const char *objc_skip_typespec(const char *type) +{ + size_t ignored = 0; + return sizeof_type(type, &ignored); +} + +const char *objc_skip_argspec(const char *type) +{ + type = objc_skip_typespec(type); + while(isdigit(*type)) { type++; } + return type; +} + +PRIVATE size_t lengthOfTypeEncoding(const char *types) +{ + if ((NULL == types) || ('\0' == types[0])) { return 0; } + const char *end = objc_skip_typespec(types); + size_t length = end - types; + return length; +} + +static char* copyTypeEncoding(const char *types) +{ + size_t length = lengthOfTypeEncoding(types); + char *copy = malloc(length + 1); + memcpy(copy, types, length); + copy[length] = '\0'; + return copy; +} + +static const char * findParameterStart(const char *types, unsigned int index) +{ + for (unsigned int i=0 ; i<index ; i++) + { + types = objc_skip_argspec(types); + if ('\0' == *types) + { + return NULL; + } + } + return types; +} + + +typedef const char *(*type_parser)(const char*, void*); + +static int parse_array(const char **type, type_parser callback, void *context) +{ + // skip [ + (*type)++; + int element_count = (int)strtol(*type, (char**)type, 10); + *type = callback(*type, context); + // skip ] + (*type)++; + return element_count; +} + +static void parse_struct_or_union(const char **type, type_parser callback, void *context, char endchar) +{ + // Skip the ( and structure name + do + { + (*type)++; + // Opaque type has no =definition + if (endchar == **type) { (*type)++; return; } + } while('=' != **type); + // Skip = + (*type)++; + + while (**type != endchar) + { + // Structure elements sometimes have their names in front of each + // element, as in {NSPoint="x"f"y"f} - We need to skip the type name + // here. + // + // TODO: In a future version we should provide a callback that lets + // users of this code get the field name + if ('"'== **type) + { + + do + { + (*type)++; + } while ('"' != **type); + // Skip the closing " + (*type)++; + } + *type = callback(*type, context); + } + // skip } + (*type)++; +} + +static void parse_union(const char **type, type_parser callback, void *context) +{ + parse_struct_or_union(type, callback, context, ')'); +} + +static void parse_struct(const char **type, type_parser callback, void *context) +{ + parse_struct_or_union(type, callback, context, '}'); +} + +inline static void round_up(size_t *v, size_t b) +{ + if (0 == b) + { + return; + } + + if (*v % b) + { + *v += b - (*v % b); + } +} +inline static size_t max(size_t v, size_t v2) +{ + return v>v2 ? v : v2; +} + +static const char *sizeof_union_field(const char *type, size_t *size); + +static const char *sizeof_type(const char *type, size_t *size) +{ + type = objc_skip_type_qualifiers(type); + switch (*type) + { + // For all primitive types, we round up the current size to the + // required alignment of the type, then add the size +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + round_up(size, (alignof(typeName) * 8));\ + *size += (sizeof(typeName) * 8);\ + return type + 1;\ + } +#define SKIP_ID 1 +#define NON_INTEGER_TYPES 1 +#include "type_encoding_cases.h" + case '@': + { + round_up(size, (alignof(id) * 8)); + *size += (sizeof(id) * 8); + if (*(type+1) == '?') + { + type++; + } + return type + 1; + } + case '?': + case 'v': return type+1; + case 'j': + { + type++; + switch (*type) + { +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + round_up(size, (alignof(_Complex typeName) * 8));\ + *size += (sizeof(_Complex typeName) * 8);\ + return type + 1;\ + } +#include "type_encoding_cases.h" + } + } + case '{': + { + const char *t = type; + parse_struct(&t, (type_parser)sizeof_type, size); + size_t align = objc_alignof_type(type); + round_up(size, align * 8); + return t; + } + case '[': + { + const char *t = type; + size_t element_size = 0; + // FIXME: aligned size + int element_count = parse_array(&t, (type_parser)sizeof_type, &element_size); + (*size) += element_size * element_count; + return t; + } + case '(': + { + const char *t = type; + size_t union_size = 0; + parse_union(&t, (type_parser)sizeof_union_field, &union_size); + *size += union_size; + return t; + } + case 'b': + { + // Consume the b + type++; + // Ignore the offset + strtol(type, (char**)&type, 10); + // Consume the element type + type++; + // Read the number of bits + *size += strtol(type, (char**)&type, 10); + return type; + } + case '^': + { + // All pointers look the same to me. + *size += sizeof(void*) * 8; + size_t ignored; + // Skip the definition of the pointeee type. + return sizeof_type(type+1, &ignored); + } + } + abort(); + return NULL; +} + +static const char *sizeof_union_field(const char *type, size_t *size) +{ + size_t field_size = 0; + const char *end = sizeof_type(type, &field_size); + *size = max(*size, field_size); + return end; +} + +static const char *alignof_type(const char *type, size_t *align) +{ + type = objc_skip_type_qualifiers(type); + switch (*type) + { + // For all primitive types, we return the maximum of the new alignment + // and the old one +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + *align = max((alignof(typeName) * 8), *align);\ + return type + 1;\ + } +#define NON_INTEGER_TYPES 1 +#define SKIP_ID 1 +#include "type_encoding_cases.h" + case '@': + { + *align = max((alignof(id) * 8), *align);\ + if (*(type+1) == '?') + { + type++; + } + return type + 1; + } + case '?': + case 'v': return type+1; + case 'j': + { + type++; + switch (*type) + { +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + *align = max((alignof(_Complex typeName) * 8), *align);\ + return type + 1;\ + } +#include "type_encoding_cases.h" + } + } + case '{': + { + const char *t = type; + parse_struct(&t, (type_parser)alignof_type, align); + return t; + } + case '(': + { + const char *t = type; + parse_union(&t, (type_parser)alignof_type, align); + return t; + } + case '[': + { + const char *t = type; + parse_array(&t, (type_parser)alignof_type, &align); + return t; + } + case 'b': + { + // Consume the b + type++; + // Ignore the offset + strtol(type, (char**)&type, 10); + // Alignment of a bitfield is the alignment of the type that + // contains it + type = alignof_type(type, align); + // Ignore the number of bits + strtol(type, (char**)&type, 10); + return type; + } + case '^': + { + *align = max((alignof(void*) * 8), *align); + // All pointers look the same to me. + size_t ignored; + // Skip the definition of the pointeee type. + return alignof_type(type+1, &ignored); + } + } + abort(); + return NULL; +} + +size_t objc_sizeof_type(const char *type) +{ + size_t size = 0; + sizeof_type(type, &size); + return size / 8; +} + +size_t objc_alignof_type (const char *type) +{ + size_t align = 0; + alignof_type(type, &align); + return align / 8; +} + +size_t objc_aligned_size(const char *type) +{ + size_t size = objc_sizeof_type(type); + size_t align = objc_alignof_type(type); + return size + (size % align); +} + +size_t objc_promoted_size(const char *type) +{ + size_t size = objc_sizeof_type(type); + return size + (size % sizeof(void*)); +} + +void method_getReturnType(Method method, char *dst, size_t dst_len) +{ + if (NULL == method) { return; } + //TODO: Coped and pasted code. Factor it out. + const char *types = method->types; + size_t length = lengthOfTypeEncoding(types); + if (length < dst_len) + { + memcpy(dst, types, length); + dst[length] = '\0'; + } + else + { + memcpy(dst, types, dst_len); + } +} + +const char *method_getTypeEncoding(Method method) +{ + if (NULL == method) { return NULL; } + return method->types; +} + + +void method_getArgumentType(Method method, + unsigned int index, + char *dst, + size_t dst_len) +{ + if (NULL == method) { return; } + const char *types = findParameterStart(method->types, index); + if (NULL == types) + { + strncpy(dst, "", dst_len); + return; + } + size_t length = lengthOfTypeEncoding(types); + if (length < dst_len) + { + memcpy(dst, types, length); + dst[length] = '\0'; + } + else + { + memcpy(dst, types, dst_len); + } +} + +unsigned method_getNumberOfArguments(Method method) +{ + if (NULL == method) { return 0; } + const char *types = method->types; + unsigned int count = 0; + while('\0' != *types) + { + types = objc_skip_argspec(types); + count++; + } + return count - 1; +} + +unsigned method_get_number_of_arguments(struct objc_method *method) +{ + return method_getNumberOfArguments(method); +} + + +char* method_copyArgumentType(Method method, unsigned int index) +{ + if (NULL == method) { return NULL; } + const char *types = findParameterStart(method->types, index); + if (NULL == types) + { + return NULL; + } + return copyTypeEncoding(types); +} + +char* method_copyReturnType(Method method) +{ + if (NULL == method) { return NULL; } + return copyTypeEncoding(method->types); +} + +unsigned objc_get_type_qualifiers (const char *type) +{ + unsigned flags = 0; +#define MAP(chr, bit) case chr: flags |= (1<<bit); break; + do + { + switch (*(type++)) + { + default: return flags; + MAP('r', 1) + MAP('n', 1) + MAP('o', 2) + MAP('N', 3) + MAP('O', 4) + MAP('V', 10) + MAP('R', 8) + } + } while (1); +} + +struct objc_struct_layout +{ + const char *original_type; + const char *type; + const char *prev_type; + unsigned int record_size; + unsigned int record_align; +}; + +// Note: The implementations of these functions is horrible. +void objc_layout_structure (const char *type, + struct objc_struct_layout *layout) +{ + layout->original_type = type; + layout->type = 0; +} + +static const char *layout_structure_callback(const char *type, struct objc_struct_layout *layout) +{ + size_t align = 0; + size_t size = 0; + const char *end = sizeof_type(type, &size); + alignof_type(type, &align); + //printf("Callback called with %s\n", type); + if (layout->prev_type < type) + { + if (layout->record_align == 0) + { + layout->record_align = align; + layout->type = type; + } + } + else + { + size_t rsize = (size_t)layout->record_size; + round_up(&rsize, align); + layout->record_size = rsize + size; + } + return end; +} + +BOOL objc_layout_structure_next_member(struct objc_struct_layout *layout) +{ + const char *end = layout->type; + layout->record_size = 0; + layout->record_align = 0; + layout->prev_type = layout->type; + const char *type = layout->original_type; + parse_struct(&type, (type_parser)layout_structure_callback, layout); + //printf("Calculated: (%s) %s %d %d\n", layout->original_type, layout->type, layout->record_size, layout->record_align); + //printf("old start %s, new start %s\n", end, layout->type); + return layout->type != end; +} + +void objc_layout_structure_get_info (struct objc_struct_layout *layout, + unsigned int *offset, + unsigned int *align, + const char **type) +{ + //printf("%p\n", layout); + *type = layout->type; + size_t off = layout->record_size / 8; + *align= layout->record_align / 8; + round_up(&off, (size_t)*align); + *offset = (unsigned int)off; +} + +#ifdef ENCODING_TESTS + +#define TEST(type) do {\ + if (alignof(type) != objc_alignof_type(@encode(type)))\ + printf("Incorrect alignment for %s: %d != %d\n", @encode(type), objc_alignof_type(@encode(type)), alignof(type));\ + if (sizeof(type) != objc_sizeof_type(@encode(type)))\ + printf("Incorrect size for %s: %d != %d\n", @encode(type), objc_sizeof_type(@encode(type)), sizeof(type));\ + } while(0) + +struct foo +{ + int a[2]; + int b:5; + struct + { + double d; + const char *str; + float e; + }c; + long long **g; + union { const char c; long long b; } h; + long long f; + _Complex int z; + _Complex double y; + char v; +}; + +typedef struct +{ + float x,y; +} Point; + +typedef struct +{ + Point a, b; +} Rect; + + +int main(void) +{ + TEST(int); + TEST(const char); + TEST(unsigned long long); + TEST(_Complex int); + TEST(struct foo); + struct objc_struct_layout layout; + + objc_layout_structure(@encode(Rect), &layout); + while (objc_layout_structure_next_member (&layout)) + { + unsigned offset; + unsigned align; + const char *ftype; + struct objc_struct_layout layout2; + objc_layout_structure_get_info (&layout, &offset, &align, &ftype); + printf("%s: offset: %d, alignment: %d\n", ftype, offset, align); + objc_layout_structure(ftype, &layout2); + while (objc_layout_structure_next_member (&layout2)) + { + objc_layout_structure_get_info (&layout2, &offset, &align, &ftype); + printf("%s: offset: %d, alignment: %d\n", ftype, offset, align); + } + } + printf("%d\n", offsetof(Rect, a.x)); + printf("%d\n", offsetof(Rect, a.y)); + printf("%d\n", offsetof(Rect, b.x)); + printf("%d\n", offsetof(Rect, b.y)); + +} +#endif diff --git a/third_party/libobjc/gc_boehm.c b/third_party/libobjc/gc_boehm.c new file mode 100644 index 0000000000000000000000000000000000000000..e0b0a110f7f5f1620418c179ca3fd20ca2945c48 --- /dev/null +++ b/third_party/libobjc/gc_boehm.c @@ -0,0 +1,733 @@ +#define GNUSTEP_LIBOBJC_NO_LEGACY +#include "objc/runtime.h" +#include "objc/toydispatch.h" +#include "class.h" +#include "ivar.h" +#include "lock.h" +#include "objc/objc-auto.h" +#include "visibility.h" +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <signal.h> +#include "gc_ops.h" +#define I_HIDE_POINTERS + + +/** + * Dispatch queue used to invoke finalizers. + */ +static dispatch_queue_t finalizer_queue; +/** + * Should finalizers be invoked in their own thread? + */ +static BOOL finalizeThreaded; +/** + * Should we do some (not 100% reliable) buffer overflow checking. + */ +static size_t canarySize; +/** + * The canary value. Used to check for overruns. When an allocation is + * finalized, we check whether it ends with this value. + */ +static uint32_t canary; +/** + * Destination to write allocation log to. This can be used to implement the + * equivalent of malloc_history + */ +static FILE *allocationLog; + +struct objc_slot* objc_get_slot(Class cls, SEL selector); + +/* + * Citing boehm-gc's README.linux: + * + * 3a) Every file that makes thread calls should define GC_LINUX_THREADS and + * _REENTRANT and then include gc.h. Gc.h redefines some of the + * pthread primitives as macros which also provide the collector with + * information it requires. + */ +#ifdef __linux__ +# define GC_LINUX_THREADS + +# ifndef _REENTRANT +# define _REENTRANT +# endif + +#endif +#include <gc/gc.h> +#include <gc/gc_typed.h> + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif +#if !__has_builtin(__sync_swap) +#define __sync_swap __sync_lock_test_and_set +#endif + +void call_cxx_destruct(id obj); + +#ifdef NO_EXECINFO +static inline void dump_stack(char *msg, void *addr) {} +#else +#include <execinfo.h> +static inline void dump_stack(char *msg, void *addr) +{ + if (NULL == allocationLog) { return; } + void *array[30]; + int frames = backtrace(array, 30); + fprintf(allocationLog, "%s %p\n", msg, addr); + fflush(allocationLog); + backtrace_symbols_fd(array, frames, fileno(allocationLog)); + fflush(allocationLog); +} +#endif + +Class dead_class; + +Class objc_lookup_class(const char*); + +GC_descr gc_typeForClass(Class cls); +void gc_setTypeForClass(Class cls, GC_descr type); + +static unsigned long collectionType(unsigned options) +{ + // Low 2 bits in GC options are used for the + return options & 3; +} + +static size_t CollectRatio = 0x10000; +static size_t CollectThreshold = 0x10000; + +void objc_set_collection_threshold(size_t threshold) +{ + CollectThreshold = threshold; +} +void objc_set_collection_ratio(size_t ratio) +{ + CollectRatio = ratio; +} + +void objc_collect(unsigned long options) +{ + size_t newAllocations = GC_get_bytes_since_gc(); + // Skip collection if we haven't allocated much memory and this is a + // collect if needed collection + if ((options & OBJC_COLLECT_IF_NEEDED) && (newAllocations < CollectThreshold)) + { + return; + } + switch (collectionType(options)) + { + case OBJC_RATIO_COLLECTION: + if (newAllocations >= CollectRatio) + { + GC_gcollect(); + } + else + { + GC_collect_a_little(); + } + break; + case OBJC_GENERATIONAL_COLLECTION: + GC_collect_a_little(); + break; + case OBJC_FULL_COLLECTION: + GC_gcollect(); + break; + case OBJC_EXHAUSTIVE_COLLECTION: + { + size_t freeBytes = 0; + while (GC_get_free_bytes() != freeBytes) + { + freeBytes = GC_get_free_bytes(); + GC_gcollect(); + } + } + } +} + +BOOL objc_collectingEnabled(void) +{ + return GC_dont_gc == 0; +} + +void objc_gc_disable(void) +{ + GC_disable(); +} +void objc_gc_enable(void) +{ + GC_enable(); +} +void* objc_gc_collectable_address(void* ptr) +{ + return GC_base(ptr); +} + +BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) +{ + return __sync_bool_compare_and_swap(objectLocation, predicate, replacement); +} +BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) +{ + return __sync_bool_compare_and_swap(objectLocation, predicate, replacement); +} + +BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} +BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} +BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} +BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} + +id objc_assign_strongCast(id val, id *ptr) +{ + *ptr = val; + return val; +} + +id objc_assign_global(id val, id *ptr) +{ + if (isGCEnabled) + { + GC_add_roots(ptr, ptr+1); + } + *ptr = val; + return val; +} + +id objc_assign_ivar(id val, id dest, ptrdiff_t offset) +{ + *(id*)((char*)dest+offset) = val; + return val; +} + +struct memmove_args +{ + void *dst; + const void *src; + size_t size; +}; + +static void* callMemmove(void *args) +{ + struct memmove_args *a = args; + memmove(a->dst, a->src, a->size); + return a->dst; +} + +void *objc_memmove_collectable(void *dst, const void *src, size_t size) +{ + // For small copies, we copy onto the stack then copy from the stack. This + // ensures that pointers are always present in a scanned region. For + // larger copies, we just lock the GC to prevent it from freeing the memory + // while the system memmove() does the real copy. The first case avoids + // the need to acquire the lock, but will perform worse for very large + // copies since we are copying the data twice (and because stack space is + // relatively scarce). + if (size < 128) + { + char buffer[128]; + memcpy(buffer, src, size); + __sync_synchronize(); + memcpy(dst, buffer, size); + // In theory, we should zero the on-stack buffer here to prevent the GC + // from seeing spurious pointers, but it's not really important because + // the contents of the buffer is duplicated on the heap and overwriting + // it will typically involve another copy to this function. This will + // not be the case if we are storing in a dead object, but that's + // probably sufficiently infrequent that we shouldn't worry about + // optimising for that case. + return dst; + } + struct memmove_args args = {dst, src, size}; + return GC_call_with_alloc_lock(callMemmove, &args); +} +/** + * Weak Pointers: + * + * To implement weak pointers, we store the hidden pointer (bits all flipped) + * in the real address. We tell the GC to zero the pointer when the associated + * object is finalized. The read barrier locks the GC to prevent it from + * freeing anything, deobfuscates the pointer (at which point it becomes a + * GC-visible on-stack pointer), and then returns it. + */ + +static void *readWeakLocked(void *ptr) +{ + void *val = *(void**)ptr; + return 0 == val ? val : REVEAL_POINTER(val); +} + +id objc_read_weak(id *location) +{ + if (!isGCEnabled) + { + return *location; + } + return GC_call_with_alloc_lock(readWeakLocked, location); +} + +id objc_assign_weak(id value, id *location) +{ + if (!isGCEnabled) + { + *location = value; + return value; + } + // Temporarily zero this pointer and get the old value + id old = __sync_swap(location, 0); + if (0 != old) + { + GC_unregister_disappearing_link((void**)location); + } + // If the value is not GC'd memory (e.g. a class), the collector will crash + // trying to collect it when you add it as the target of a disappearing + // link. + if (0 != GC_base(value)) + { + GC_GENERAL_REGISTER_DISAPPEARING_LINK((void**)location, value); + } + // If some other thread has modified this, then we may have two different + // objects registered to make this pointer 0 if either is destroyed. This + // would be bad, so we need to make sure that we unregister them and + // register the correct one. + if (!__sync_bool_compare_and_swap(location, old, (id)HIDE_POINTER(value))) + { + return objc_assign_weak(value, location); + } + return value; +} + +static SEL finalize; +Class zombie_class; + + +static void runFinalize(void *addr, void *context) +{ + dump_stack("Freeing Object: ", addr); + id obj = addr; + size_t size = (uintptr_t)context; + if ((canarySize > 0) && + (*(uint32_t*)((char*)obj + size) != canary)) + { + fprintf(stderr, "Something wrote past the end of %p\n", addr); + if (obj->isa != Nil) + { + fprintf(stderr, "Instance of %s\n", obj->isa->name); + } + abort(); + } + //fprintf(stderr, "FINALIZING %p (%s)\n", addr, ((id)addr)->isa->name); + if (Nil == obj->isa) { return; } + struct objc_slot *slot = objc_get_slot(obj->isa, finalize); + if (NULL != slot) + { + slot->method(obj, finalize); + } + call_cxx_destruct(obj); + *(void**)addr = zombie_class; +} + +static void collectIvarForClass(Class cls, GC_word *bitmap) +{ + for (unsigned i=0 ; (cls->ivars != 0) && (i<cls->ivars->count) ; i++) + { + struct objc_ivar *ivar = &cls->ivars->ivar_list[i]; + size_t start = ivar->offset; + size_t end = i+1 < cls->ivars->count ? cls->ivars->ivar_list[i+1].offset + : cls->instance_size; + switch (ivar->type[0]) + { + case '[': case '{': case '(': + // If the structure / array / union type doesn't contain any + // pointers, then skip it. We still need to be careful of packed + if ((strchr(ivar->type, '^') == 0) && + (strchr(ivar->type, '@') == 0)) + { + break; + } + // Explicit pointer types + case '^': case '@': + for (unsigned b=(start / sizeof(void*)) ; b<(end/sizeof(void*)) ; b++) + { + GC_set_bit(bitmap, b); + } + } + } + if (cls->super_class) + { + collectIvarForClass(cls->super_class, bitmap); + } +} + +static GC_descr descriptor_for_class(Class cls) +{ + GC_descr descr = gc_typeForClass(cls); + + if (0 != descr) { return descr; } + + LOCK_RUNTIME_FOR_SCOPE(); + + descr = (GC_descr)gc_typeForClass(cls); + if (0 != descr) { return descr; } + + size_t size = cls->instance_size / 8 + 1; + GC_word bitmap[size]; + memset(bitmap, 0, size); + collectIvarForClass(cls, bitmap); + // It's safe to round down here - if a class ends with an ivar that is + // smaller than a pointer, then it can't possibly be a pointer. + //fprintf(stderr, "Class is %d byes, %d words\n", cls->instance_size, cls->instance_size/sizeof(void*)); + descr = GC_make_descriptor(bitmap, cls->instance_size / sizeof(void*)); + gc_setTypeForClass(cls, descr); + return descr; +} + +static id allocate_class(Class cls, size_t extra) +{ + size_t size = class_getInstanceSize(cls); + if (canarySize) + { + extra += 4; + } + id obj = 0; + // If there are some extra bytes, they may contain pointers, so we ignore + // the type + if (extra > 0) + { + size += extra; + // FIXME: Overflow checking! + obj = GC_MALLOC(size); + } + else + { + obj = GC_MALLOC_EXPLICITLY_TYPED(size, descriptor_for_class(cls)); + } + //fprintf(stderr, "Allocating %p (%s + %d). Base is %p\n", obj, cls->name, extra, GC_base(obj)); + // It would be nice not to register a finaliser if the object didn't + // implement finalize or .cxx_destruct methods. Unfortunately, this is not + // possible, because a class may add a finalize method as it runs. + GC_REGISTER_FINALIZER_NO_ORDER(obj, runFinalize, + (void*)(uintptr_t)size-canarySize, 0, 0); + if (canarySize > 0) + { + *(uint32_t*)((char*)obj + size - canarySize) = canary; + } + dump_stack("Allocating object", obj); + return obj; +} + +static void free_object(id obj) {} +id objc_allocate_object(Class cls, int extra) +{ + return class_createInstance(cls, extra); +} + +static void registerThread(BOOL errorOnNotRegistered) +{ + struct GC_stack_base base; + if (GC_get_stack_base(&base) != GC_SUCCESS) + { + fprintf(stderr, "Unable to find stack base for new thread\n"); + abort(); + } + switch (GC_register_my_thread(&base)) + { + case GC_SUCCESS: + if (errorOnNotRegistered) + { + fprintf(stderr, "Thread should have already been registered with the GC\n"); + } + case GC_DUPLICATE: + return; + case GC_NO_THREADS: + case GC_UNIMPLEMENTED: + fprintf(stderr, "Unable to register stack\n"); + abort(); + } +} + +void objc_registerThreadWithCollector(void) +{ + registerThread(NO); +} +void objc_unregisterThreadWithCollector(void) +{ + GC_unregister_my_thread(); +} +void objc_assertRegisteredThreadWithCollector() +{ + registerThread(YES); +} + +/** + * Structure stored for each GC + */ +static struct gc_refcount +{ + /** Reference count */ + intptr_t refCount; + /** Strong pointer */ + id ptr; +} null_refcount = {0}; + +static int refcount_compare(const void *ptr, struct gc_refcount rc) +{ + return ptr == rc.ptr; +} +static uint32_t ptr_hash(const void *ptr) +{ + // Bit-rotate right 4, since the lowest few bits in an object pointer will + // always be 0, which is not so useful for a hash value + return ((uintptr_t)ptr >> 4) | ((uintptr_t)ptr << ((sizeof(id) * 8) - 4)); +} +static uint32_t refcount_hash(struct gc_refcount rc) +{ + return ptr_hash(rc.ptr); +} +static int isEmpty(struct gc_refcount rc) +{ + return rc.ptr == NULL; +} +#define MAP_TABLE_VALUE_NULL isEmpty +#define MAP_TABLE_NAME refcount +#define MAP_TABLE_COMPARE_FUNCTION refcount_compare +#define MAP_TABLE_HASH_KEY ptr_hash +#define MAP_TABLE_HASH_VALUE refcount_hash +#define MAP_TABLE_VALUE_TYPE struct gc_refcount +#define MAP_TABLE_VALUE_PLACEHOLDER null_refcount +#define MAP_TABLE_TYPES_BITMAP (1<<(offsetof(struct gc_refcount, ptr) / sizeof(void*))) +#define MAP_TABLE_ACCESS_BY_REFERENCE +#include "hash_table.h" + +static refcount_table *refcounts; + +id objc_gc_retain(id object) +{ + struct gc_refcount *refcount = refcount_table_get(refcounts, object); + if (NULL == refcount) + { + LOCK_FOR_SCOPE(&(refcounts->lock)); + refcount = refcount_table_get(refcounts, object); + if (NULL == refcount) + { + struct gc_refcount rc = { 1, object}; + refcount_insert(refcounts, rc); + return object; + } + } + __sync_fetch_and_add(&(refcount->refCount), 1); + return object; +} +void objc_gc_release(id object) +{ + struct gc_refcount *refcount = refcount_table_get(refcounts, object); + // This object has not been explicitly retained, don't release it + if (0 == refcount) { return; } + + if (0 == __sync_sub_and_fetch(&(refcount->refCount), 1)) + { + LOCK_FOR_SCOPE(&(refcounts->lock)); + refcount->ptr = 0; + __sync_synchronize(); + // If another thread has incremented the reference count while we were + // doing this, then we need to add the count back into the table, + // otherwise we can carry on. + if (!__sync_bool_compare_and_swap(&(refcount->refCount), 0, 0)) + { + refcount->ptr = object; + } + } +} +int objc_gc_retain_count(id object) +{ + struct gc_refcount *refcount = refcount_table_get(refcounts, object); + return (0 == refcount) ? 0 : refcount->refCount; +} + + +static void nuke_buffer(void *addr, void *s) +{ + return; + dump_stack("Freeing allocation: ", addr); + uintptr_t size = (uintptr_t)s; + if (canary != *(uint32_t*)((char*)addr + size)) + { + fprintf(stderr, + "Something wrote past the end of memory allocation %p\n", + addr); + abort(); + } + memset(addr, 0, size); +} + +void* objc_gc_allocate_collectable(size_t size, BOOL isScanned) +{ + void *buffer; + if (isScanned) + { + buffer = GC_MALLOC(size+canarySize); + } + else + { + buffer = GC_MALLOC_ATOMIC(size+canarySize); + memset(buffer, 0, size); + } + if (canarySize > 0) + { + *(uint32_t*)((char*)buffer + size) = canary; + GC_REGISTER_FINALIZER_NO_ORDER(buffer, nuke_buffer, + (void*)(uintptr_t)size, 0, 0); + } + dump_stack("Allocating memory", buffer); + return buffer; +} +void* objc_gc_reallocate_collectable(void *ptr, size_t size, BOOL isScanned) +{ + if (0 == size) { return 0; } + void *new = isScanned ? GC_MALLOC(size) : GC_MALLOC_ATOMIC(size); + + if (0 == new) { return 0; } + + if (NULL != ptr) + { + size_t oldSize = GC_size(ptr); + if (oldSize < size) + { + size = oldSize; + } + memcpy(new, ptr, size); + } + dump_stack("New allocation from realloc: ", new); + return new; +} + +static void collectAndDumpStats(int signalNo) +{ + objc_collect(OBJC_EXHAUSTIVE_COLLECTION); + GC_dump(); +} + +static void deferredFinalizer(void) +{ + GC_invoke_finalizers(); +} + +static void runFinalizers(void) +{ + //fprintf(stderr, "RUNNING FINALIZERS\n"); + if (finalizeThreaded) + { + dispatch_async_f(finalizer_queue, deferredFinalizer, NULL); + } + else + { + GC_invoke_finalizers(); + } +} + +PRIVATE void init_gc(void) +{ + //GC_no_dls = 1; + //GC_enable_incremental(); + GC_INIT(); + char *envValue; + // Dump GC stats on exit - uncomment when debugging. + if (getenv("LIBOBJC_DUMP_GC_STATUS_ON_EXIT")) + { + atexit(GC_dump); + } + if ((envValue = getenv("LIBOBJC_LOG_ALLOCATIONS"))) + { + allocationLog = fopen(envValue, "a"); + } + if ((envValue = getenv("LIBOBJC_CANARIES"))) + { + unsigned s = envValue[0] ? strtol(envValue, NULL, 10) : 123; + srandom(s); + canarySize = sizeof(uint32_t); + canary = random(); + } + if ((envValue = getenv("LIBOBJC_DUMP_GC_STATUS_ON_SIGNAL"))) + { + int s = envValue[0] ? (int)strtol(envValue, NULL, 10) : SIGUSR2; + signal(s, collectAndDumpStats); + } + //GC_clear_roots(); +} + +BOOL objc_collecting_enabled(void) +{ + // Lock the GC in the current state once it's been queried. This prevents + // the loading of any modules with an incompatible GC mode. + current_gc_mode = isGCEnabled ? GC_Required : GC_None; + return isGCEnabled; +} + +void objc_startCollectorThread(void) +{ + if (YES == finalizeThreaded) { return; } + finalizer_queue = dispatch_queue_create("ObjC finalizeation thread", 0); + finalizeThreaded = YES; +} + +void objc_clear_stack(unsigned long options) +{ + // This isn't a very good implementation - we should really be working out + // how much stack space is left somehow, but this is not possible to do + // portably. + int i[1024]; + int *addr = &i[0]; + memset(addr, 0, 1024); + // Tell the compiler that something that it doesn't know about is touching + // this memory, so it shouldn't optimise the allocation and memset away. + __asm__ volatile ("" : : "m"(addr) : "memory"); + +} +BOOL objc_is_finalized(void *ptr) +{ + return *(Class*)ptr == zombie_class; +} +// FIXME: Stub implementation that should be replaced with something better +void objc_finalizeOnMainThread(Class cls) {} + +static void *debug_malloc(size_t s) +{ + return GC_MALLOC_UNCOLLECTABLE(s); +} +static void debug_free(void *ptr) +{ + GC_FREE(ptr); +} + +PRIVATE struct gc_ops gc_ops_boehm = +{ + .allocate_class = allocate_class, + .free_object = free_object, + .malloc = debug_malloc, + .free = debug_free, +}; + +extern struct objc_class _NSConcreteStackBlock; +void *_Block_copy(void *src); + +PRIVATE void enableGC(BOOL exclude) +{ + isGCEnabled = YES; + gc = &gc_ops_boehm; + refcount_initialize(&refcounts, 4096); + finalize = sel_registerName("finalize"); + GC_finalizer_notifier = runFinalizers; +} diff --git a/third_party/libobjc/gc_none.c b/third_party/libobjc/gc_none.c new file mode 100644 index 0000000000000000000000000000000000000000..ff4331d737af8fac947aba54a4e7e867d868a4b2 --- /dev/null +++ b/third_party/libobjc/gc_none.c @@ -0,0 +1,43 @@ +#include "visibility.h" +#include "objc/runtime.h" +#include "gc_ops.h" +#include "class.h" +#include <stdlib.h> +#include <stdio.h> + +static id allocate_class(Class cls, size_t extraBytes) +{ + intptr_t *addr = calloc(cls->instance_size + extraBytes + sizeof(intptr_t), 1); + return (id)(addr + 1); +} + +static void free_object(id obj) +{ + free((void*)(((intptr_t*)obj) - 1)); +} + +static void *alloc(size_t size) +{ + return calloc(size, 1); +} + +PRIVATE struct gc_ops gc_ops_none = +{ + .allocate_class = allocate_class, + .free_object = free_object, + .malloc = alloc, + .free = free +}; +PRIVATE struct gc_ops *gc = &gc_ops_none; + +PRIVATE BOOL isGCEnabled = NO; + +#ifndef ENABLE_GC +PRIVATE void enableGC(BOOL exclusive) +{ + fprintf(stderr, "Attempting to enable garbage collection, but your" + "Objective-C runtime was built without garbage collection" + "support\n"); + abort(); +} +#endif diff --git a/third_party/libobjc/gc_ops.h b/third_party/libobjc/gc_ops.h new file mode 100644 index 0000000000000000000000000000000000000000..5bd35a3ed5cd40599985f8078d259a2d75a02748 --- /dev/null +++ b/third_party/libobjc/gc_ops.h @@ -0,0 +1,79 @@ + +/** + * Garbage collection operations. + */ +struct gc_ops +{ + /** + * Initialises this collector. + */ + void (*init)(void); + /** + * Allocates enough space for a class, followed by some extra bytes. + */ + id (*allocate_class)(Class, size_t); + /** + * Frees an object. + */ + void (*free_object)(id); + /** + * Allocates some memory that can be used to store pointers. This must be + * used instead of malloc() for internal data structures that will store + * pointers passed in from outside. The function is expected to zero the + * memory that it returns. + */ + void* (*malloc)(size_t); + /** + * Frees some memory that was previously used to store pointers. + */ + void (*free)(void*); +}; + +/** + * Enables garbage collection, if it isn't already enabled. + * + * If the exclusive flag is set, then this will ensure that all -retain / + * -release / -autorelease messages become no-ops. + */ +void enableGC(BOOL exclusive); + +/** + * The mode for garbage collection + */ +enum objc_gc_mode +{ + /** This module neither uses, nor supports, garbage collection. */ + GC_None = 0, + /** + * This module uses garbage collection, but also sends retain / release + * messages. It can be used with or without GC. + */ + GC_Optional = 1, + /** + * This module expects garbage collection and will break without it. + */ + GC_Required = 2, + /** + * This module was compiled with automatic reference counting. This + * guarantees the use of the non-fragile ABI and means that we could + * potentially support GC, although we don't currently. + */ + GC_ARC = 3 +}; + +/** + * The current Objective-C garbage collection mode. + */ +extern enum objc_gc_mode current_gc_mode; +/** + * Have we loaded any code that triggers the ObjC GC support? + */ +extern BOOL isGCEnabled; + +/** + * The current set of garbage collector operations to use. + */ +extern struct gc_ops *gc; + +extern struct gc_ops gc_ops_boehm; +extern struct gc_ops gc_ops_none; diff --git a/third_party/libobjc/hash_table.c b/third_party/libobjc/hash_table.c new file mode 100644 index 0000000000000000000000000000000000000000..ee9cc001a2a600e418947de317ba666cd40521b9 --- /dev/null +++ b/third_party/libobjc/hash_table.c @@ -0,0 +1,22 @@ +#ifndef ENABLE_GC +#include "objc/toydispatch.h" +#include "lock.h" +#include "visibility.h" + + +static dispatch_queue_t garbage_queue; + +PRIVATE void objc_collect_garbage_data(void(*cleanup)(void*), void *garbage) +{ + if (0 == garbage_queue) + { + LOCK_RUNTIME_FOR_SCOPE(); + if (0 == garbage_queue) + { + garbage_queue = dispatch_queue_create("ObjC deferred free queue", 0); + } + } + dispatch_async_f(garbage_queue, garbage, cleanup); +} + +#endif diff --git a/third_party/libobjc/hash_table.h b/third_party/libobjc/hash_table.h new file mode 100644 index 0000000000000000000000000000000000000000..e064af1782a2daac76e3e9145e53d40abd18e0e0 --- /dev/null +++ b/third_party/libobjc/hash_table.h @@ -0,0 +1,560 @@ +/** + * hash_table.h provides a template for implementing hopscotch hash tables. + * + * Several macros must be defined before including this file: + * + * MAP_TABLE_NAME defines the name of the table. All of the operations and + * types related to this table will be prefixed with this value. + * + * MAP_TABLE_COMPARE_FUNCTION defines the function used for testing a key + * against a value in the table for equality. This must take two void* + * arguments. The first is the key and the second is the value. + * + * MAP_TABLE_HASH_KEY and MAP_TABLE_HASH_VALUE define a pair of functions that + * takes a key and a value pointer respectively as their argument and returns + * an int32_t representing the hash. + * + * Optionally, MAP_TABLE_STATIC_SIZE may be defined, to define a table type + * which has a static size. + */ +#include "lock.h" +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + + +#ifdef ENABLE_GC +# include <gc/gc.h> +# include <gc/gc_typed.h> +# define CALLOC(x,y) GC_MALLOC(x*y) +# define IF_NO_GC(x) +# define IF_GC(x) x +#else +# define CALLOC(x,y) calloc(x,y) +# define IF_NO_GC(x) x +# define IF_GC(x) +#endif + +#ifndef MAP_TABLE_NAME +# error You must define MAP_TABLE_NAME. +#endif +#ifndef MAP_TABLE_COMPARE_FUNCTION +# error You must define MAP_TABLE_COMPARE_FUNCTION. +#endif +#ifndef MAP_TABLE_HASH_KEY +# error You must define MAP_TABLE_HASH_KEY +#endif +#ifndef MAP_TABLE_HASH_VALUE +# error You must define MAP_TABLE_HASH_VALUE +#endif + +// Horrible multiple indirection to satisfy the weird precedence rules in cpp +#define REALLY_PREFIX_SUFFIX(x,y) x ## y +#define PREFIX_SUFFIX(x, y) REALLY_PREFIX_SUFFIX(x, y) + +/** + * PREFIX(x) macro adds the table name prefix to the argument. + */ +#define PREFIX(x) PREFIX_SUFFIX(MAP_TABLE_NAME, x) + + +/** + * Map tables are protected by a lock by default. Defining MAP_TABLE_NO_LOCK + * will prevent this and make you responsible for synchronization. + */ +#ifdef MAP_TABLE_NO_LOCK +# define MAP_LOCK() +# define MAP_UNLOCK() +#else +# define MAP_LOCK() (LOCK(&table->lock)) +# define MAP_UNLOCK() (UNLOCK(&table->lock)) +#endif +#ifndef MAP_TABLE_VALUE_TYPE +# define MAP_TABLE_VALUE_TYPE void* +static BOOL PREFIX(_is_null)(void *value) +{ + return value == NULL; +} +# define MAP_TABLE_TYPES_BITMAP 1 +# define MAP_TABLE_VALUE_NULL PREFIX(_is_null) +# define MAP_TABLE_VALUE_PLACEHOLDER NULL +#endif + +typedef struct PREFIX(_table_cell_struct) +{ + uint32_t secondMaps; + MAP_TABLE_VALUE_TYPE value; +} *PREFIX(_table_cell); + +#ifdef MAP_TABLE_STATIC_SIZE +typedef struct +{ + mutex_t lock; + unsigned int table_used; + IF_NO_GC(unsigned int enumerator_count;) + struct PREFIX(_table_cell_struct) table[MAP_TABLE_STATIC_SIZE]; +} PREFIX(_table); +static PREFIX(_table) MAP_TABLE_STATIC_NAME; +# ifndef MAP_TABLE_NO_LOCK +__attribute__((constructor)) void static PREFIX(_table_initializer)(void) +{ + INIT_LOCK(MAP_TABLE_STATIC_NAME.lock); +} +# endif +# define TABLE_SIZE(x) MAP_TABLE_STATIC_SIZE +#else +typedef struct PREFIX(_table_struct) +{ + mutex_t lock; + unsigned int table_size; + unsigned int table_used; + IF_NO_GC(unsigned int enumerator_count;) +# if defined(ENABLE_GC) && defined(MAP_TABLE_TYPES_BITMAP) + GC_descr descr; +# endif + struct PREFIX(_table_struct) *old; + struct PREFIX(_table_cell_struct) *table; +} PREFIX(_table); + +struct PREFIX(_table_cell_struct) *PREFIX(alloc_cells)(PREFIX(_table) *table, int count) +{ +# if defined(ENABLE_GC) && defined(MAP_TABLE_TYPES_BITMAP) + return GC_CALLOC_EXPLICITLY_TYPED(count, + sizeof(struct PREFIX(_table_cell_struct)), table->descr); +# else + return CALLOC(count, sizeof(struct PREFIX(_table_cell_struct))); +# endif +} + +PREFIX(_table) *PREFIX(_create)(uint32_t capacity) +{ + PREFIX(_table) *table = CALLOC(1, sizeof(PREFIX(_table))); +# ifndef MAP_TABLE_NO_LOCK + INIT_LOCK(table->lock); +# endif +# if defined(ENABLE_GC) && defined(MAP_TABLE_TYPES_BITMAP) + // The low word in the bitmap stores the offsets of the next entries + GC_word bitmap = (MAP_TABLE_TYPES_BITMAP << 1); + table->descr = GC_make_descriptor(&bitmap, + sizeof(struct PREFIX(_table_cell_struct)) / sizeof (void*)); +# endif + table->table = PREFIX(alloc_cells)(table, capacity); + table->table_size = capacity; + return table; +} + +void PREFIX(_initialize)(PREFIX(_table) **table, uint32_t capacity) +{ +#ifdef ENABLE_GC + GC_add_roots(table, table+1); +#endif + *table = PREFIX(_create)(capacity); +} + +# define TABLE_SIZE(x) (x->table_size) +#endif + + +#ifdef MAP_TABLE_STATIC_SIZE +static int PREFIX(_table_resize)(PREFIX(_table) *table) +{ + return 0; +} +#else + +static int PREFIX(_insert)(PREFIX(_table) *table, MAP_TABLE_VALUE_TYPE value); + +static int PREFIX(_table_resize)(PREFIX(_table) *table) +{ + struct PREFIX(_table_cell_struct) *newArray = + PREFIX(alloc_cells)(table, table->table_size * 2); + if (NULL == newArray) { return 0; } + + // Allocate a new table structure and move the array into that. Now + // lookups will try using that one, if possible. + PREFIX(_table) *copy = CALLOC(1, sizeof(PREFIX(_table))); + memcpy(copy, table, sizeof(PREFIX(_table))); + table->old = copy; + + // Now we make the original table structure point to the new (empty) array. + table->table = newArray; + table->table_size *= 2; + // The table currently has no entries; the copy has them all. + table->table_used = 0; + + // Finally, copy everything into the new table + // Note: we should really do this in a background thread. At this stage, + // we can do the updates safely without worrying about read contention. + int copied = 0; + for (uint32_t i=0 ; i<copy->table_size ; i++) + { + MAP_TABLE_VALUE_TYPE value = copy->table[i].value; + if (!MAP_TABLE_VALUE_NULL(value)) + { + copied++; + PREFIX(_insert)(table, value); + } + } + __sync_synchronize(); + table->old = NULL; +# if !defined(ENABLE_GC) && defined(MAP_TABLE_SINGLE_THREAD) + free(copy); +# endif + return 1; +} +#endif + +struct PREFIX(_table_enumerator) +{ + PREFIX(_table) *table; + unsigned int seen; + unsigned int index; +}; + +static inline PREFIX(_table_cell) PREFIX(_table_lookup)(PREFIX(_table) *table, + uint32_t hash) +{ + hash = hash % TABLE_SIZE(table); + return &table->table[hash]; +} + +static int PREFIX(_table_move_gap)(PREFIX(_table) *table, uint32_t fromHash, + uint32_t toHash, PREFIX(_table_cell) emptyCell) +{ + for (uint32_t hash = fromHash - 32 ; hash < fromHash ; hash++) + { + // Get the cell n before the hash. + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash); + // If this node is a primary entry move it down + if (MAP_TABLE_HASH_VALUE(cell->value) == hash) + { + emptyCell->value = cell->value; + cell->secondMaps |= (1 << ((fromHash - hash) - 1)); + cell->value = MAP_TABLE_VALUE_PLACEHOLDER; + if (hash - toHash < 32) + { + return 1; + } + return PREFIX(_table_move_gap)(table, hash, toHash, cell); + } + int hop = __builtin_ffs(cell->secondMaps); + if (hop > 0 && (hash + hop) < fromHash) + { + PREFIX(_table_cell) hopCell = PREFIX(_table_lookup)(table, hash+hop); + emptyCell->value = hopCell->value; + // Update the hop bit for the new offset + cell->secondMaps |= (1 << ((fromHash - hash) - 1)); + // Clear the hop bit in the original cell + cell->secondMaps &= ~(1 << (hop - 1)); + hopCell->value = MAP_TABLE_VALUE_PLACEHOLDER; + if (hash - toHash < 32) + { + return 1; + } + return PREFIX(_table_move_gap)(table, hash + hop, toHash, hopCell); + } + } + return 0; +} +static int PREFIX(_table_rebalance)(PREFIX(_table) *table, uint32_t hash) +{ + for (unsigned i=32 ; i<TABLE_SIZE(table) ; i++) + { + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash + i); + if (MAP_TABLE_VALUE_NULL(cell->value)) + { + // We've found a free space, try to move it up. + return PREFIX(_table_move_gap)(table, hash + i, hash, cell); + } + } + return 0; +} + +__attribute__((unused)) +static int PREFIX(_insert)(PREFIX(_table) *table, + MAP_TABLE_VALUE_TYPE value) +{ + MAP_LOCK(); + uint32_t hash = MAP_TABLE_HASH_VALUE(value); + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash); + if (MAP_TABLE_VALUE_NULL(cell->value)) + { + cell->secondMaps = 0; + cell->value = value; + table->table_used++; + MAP_UNLOCK(); + return 1; + } + /* If this cell is full, try the next one. */ + for (unsigned int i=0 ; i<32 ; i++) + { + PREFIX(_table_cell) second = + PREFIX(_table_lookup)(table, hash+i); + if (MAP_TABLE_VALUE_NULL(second->value)) + { + cell->secondMaps |= (1 << (i-1)); + second->value = value; + table->table_used++; + MAP_UNLOCK(); + return 1; + } + } + /* If the table is full, or nearly full, then resize it. Note that we + * resize when the table is at 80% capacity because it's cheaper to copy + * everything than spend the next few updates shuffling everything around + * to reduce contention. A hopscotch hash table starts to degrade in + * performance at around 90% capacity, so stay below that. + */ + if (table->table_used > (0.8 * TABLE_SIZE(table))) + { + PREFIX(_table_resize)(table); + MAP_UNLOCK(); + return PREFIX(_insert)(table, value); + } + /* If this virtual cell is full, rebalance the hash from this point and + * try again. */ + if (PREFIX(_table_rebalance)(table, hash)) + { + MAP_UNLOCK(); + return PREFIX(_insert)(table, value); + } + /** If rebalancing failed, resize even if we are <80% full. This can + * happen if your hash function sucks. If you don't want this to happen, + * get a better hash function. */ + if (PREFIX(_table_resize)(table)) + { + MAP_UNLOCK(); + return PREFIX(_insert)(table, value); + } + fprintf(stderr, "Insert failed\n"); + MAP_UNLOCK(); + return 0; +} + +static void *PREFIX(_table_get_cell)(PREFIX(_table) *table, const void *key) +{ + uint32_t hash = MAP_TABLE_HASH_KEY(key); + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash); + // Value does not exist. + if (!MAP_TABLE_VALUE_NULL(cell->value)) + { + if (MAP_TABLE_COMPARE_FUNCTION(key, cell->value)) + { + return cell; + } + uint32_t jump = cell->secondMaps; + // Look at each offset defined by the jump table to find the displaced location. + for (int hop = __builtin_ffs(jump) ; hop > 0 ; hop = __builtin_ffs(jump)) + { + PREFIX(_table_cell) hopCell = PREFIX(_table_lookup)(table, hash+hop); + if (MAP_TABLE_COMPARE_FUNCTION(key, hopCell->value)) + { + return hopCell; + } + // Clear the most significant bit and try again. + jump &= ~(1 << (hop-1)); + } + } +#ifndef MAP_TABLE_STATIC_SIZE + if (table->old) + { + return PREFIX(_table_get_cell)(table->old, key); + } +#endif + return NULL; +} + +__attribute__((unused)) +static void PREFIX(_table_move_second)(PREFIX(_table) *table, + PREFIX(_table_cell) emptyCell) +{ + uint32_t jump = emptyCell->secondMaps; + // Look at each offset defined by the jump table to find the displaced location. + int hop = __builtin_ffs(jump); + PREFIX(_table_cell) hopCell = + PREFIX(_table_lookup)(table, MAP_TABLE_HASH_VALUE(emptyCell->value) + hop); + emptyCell->value = hopCell->value; + emptyCell->secondMaps &= ~(1 << (hop-1)); + if (0 == hopCell->secondMaps) + { + hopCell->value = MAP_TABLE_VALUE_PLACEHOLDER; + } + else + { + PREFIX(_table_move_second)(table, hopCell); + } +} +__attribute__((unused)) +static void PREFIX(_remove)(PREFIX(_table) *table, void *key) +{ + MAP_LOCK(); + PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); + if (NULL == cell) { return; } + // If the cell contains a value, set it to the placeholder and shuffle up + // everything + if (0 == cell->secondMaps) + { + cell->value = MAP_TABLE_VALUE_PLACEHOLDER; + } + else + { + PREFIX(_table_move_second)(table, cell); + } + table->table_used--; + MAP_UNLOCK(); +} + +__attribute__((unused)) +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +static MAP_TABLE_VALUE_TYPE* +#else +static MAP_TABLE_VALUE_TYPE +#endif + PREFIX(_table_get)(PREFIX(_table) *table, + const void *key) +{ + PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); + if (NULL == cell) + { +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return NULL; +#else + return MAP_TABLE_VALUE_PLACEHOLDER; +#endif + } +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return &cell->value; +#else + return cell->value; +#endif +} +__attribute__((unused)) +static void PREFIX(_table_set)(PREFIX(_table) *table, const void *key, + MAP_TABLE_VALUE_TYPE value) +{ + PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); + if (NULL == cell) + { + PREFIX(_insert)(table, value); + } + cell->value = value; +} + +__attribute__((unused)) +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +static MAP_TABLE_VALUE_TYPE* +#else +static MAP_TABLE_VALUE_TYPE +#endif +PREFIX(_next)(PREFIX(_table) *table, + struct PREFIX(_table_enumerator) **state) +{ + if (NULL == *state) + { + *state = CALLOC(1, sizeof(struct PREFIX(_table_enumerator))); + // Make sure that we are not reallocating the table when we start + // enumerating + MAP_LOCK(); + (*state)->table = table; + (*state)->index = -1; + IF_NO_GC(__sync_fetch_and_add(&table->enumerator_count, 1);) + MAP_UNLOCK(); + } + if ((*state)->seen >= (*state)->table->table_used) + { +#ifndef ENABLE_GC + MAP_LOCK(); + __sync_fetch_and_sub(&table->enumerator_count, 1); + MAP_UNLOCK(); + free(*state); +#endif +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return NULL; +#else + return MAP_TABLE_VALUE_PLACEHOLDER; +#endif + } + while ((++((*state)->index)) < TABLE_SIZE((*state)->table)) + { + if (!MAP_TABLE_VALUE_NULL((*state)->table->table[(*state)->index].value)) + { + (*state)->seen++; +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return &(*state)->table->table[(*state)->index].value; +#else + return (*state)->table->table[(*state)->index].value; +#endif + } + } +#ifndef ENABLE_GC + // Should not be reached, but may be if the table is unsafely modified. + MAP_LOCK(); + table->enumerator_count--; + MAP_UNLOCK(); + free(*state); +#endif +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return NULL; +#else + return MAP_TABLE_VALUE_PLACEHOLDER; +#endif +} +/** + * Returns the current value for an enumerator. This is used when you remove + * objects during enumeration. It may cause others to be shuffled up the + * table. + */ +__attribute__((unused)) +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +static MAP_TABLE_VALUE_TYPE* +#else +static MAP_TABLE_VALUE_TYPE +#endif +PREFIX(_current)(PREFIX(_table) *table, + struct PREFIX(_table_enumerator) **state) +{ +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return &(*state)->table->table[(*state)->index].value; +#else + return (*state)->table->table[(*state)->index].value; +#endif +} + +#undef TABLE_SIZE +#undef REALLY_PREFIX_SUFFIX +#undef PREFIX_SUFFIX +#undef PREFIX + +#undef MAP_TABLE_NAME +#undef MAP_TABLE_COMPARE_FUNCTION +#undef MAP_TABLE_HASH_KEY +#undef MAP_TABLE_HASH_VALUE + +#ifdef MAP_TABLE_STATIC_SIZE +# undef MAP_TABLE_STATIC_SIZE +#endif + +#undef MAP_TABLE_VALUE_TYPE + +#undef MAP_LOCK +#undef MAP_UNLOCK +#ifdef MAP_TABLE_NO_LOCK +# undef MAP_TABLE_NO_LOCK +#endif + +#ifdef MAP_TABLE_SINGLE_THREAD +# undef MAP_TABLE_SINGLE_THREAD +#endif + +#undef MAP_TABLE_VALUE_NULL +#undef MAP_TABLE_VALUE_PLACEHOLDER + +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +# undef MAP_TABLE_ACCESS_BY_REFERENCE +#endif + +#undef CALLOC +#undef IF_NO_GC +#undef IF_GC +#undef MAP_TABLE_TYPES_BITMAP diff --git a/third_party/libobjc/hooks.c b/third_party/libobjc/hooks.c new file mode 100644 index 0000000000000000000000000000000000000000..8def49b897a237f77c5feac296ffbde8f22572e4 --- /dev/null +++ b/third_party/libobjc/hooks.c @@ -0,0 +1,3 @@ +#include "objc/runtime.h" +#define OBJC_HOOK +#include "objc/hooks.h" diff --git a/third_party/libobjc/ivar.c b/third_party/libobjc/ivar.c new file mode 100644 index 0000000000000000000000000000000000000000..3c639aa35baa66a3948a25326aed4b92c71b241a --- /dev/null +++ b/third_party/libobjc/ivar.c @@ -0,0 +1,143 @@ +#include <stdio.h> +#include <stdlib.h> +#include "objc/runtime.h" +#include "class.h" +#include "ivar.h" +#include "visibility.h" + +ptrdiff_t objc_alignof_type(const char *); +ptrdiff_t objc_sizeof_type(const char *); + +PRIVATE void objc_compute_ivar_offsets(Class class) +{ + int i = 0; + /* If this class was compiled with support for late-bound ivars, the + * instance_size field will contain 0 - {the size of the instance variables + * declared for just this class}. The individual instance variable offset + * fields will then be the offsets from the start of the class, and so must + * have the size of the parent class prepended. */ + if (class->instance_size <= 0) + { + Class super = class_getSuperclass(class); + long ivar_start = 0; + if (Nil != super) + { + if (super->instance_size <= 0) + { + objc_compute_ivar_offsets(super); + } + ivar_start = super->instance_size; + } + class->instance_size = ivar_start - class->instance_size; + /* For each instance variable, we add the offset if required (it will be zero + * if this class is compiled with a static ivar layout). We then set the + * value of a global variable to the offset value. + * + * Any class compiled with support for the non-fragile ABI, but not actually + * using it, will export the ivar offset field as a symbol. + * + * Note that using non-fragile ivars breaks @defs(). If you need equivalent + * functionality, provide an alternative @interface with all variables + * declared @public. + */ + if (class->ivars) + { + for (i = 0 ; i < class->ivars->count ; i++) + { + struct objc_ivar *ivar = &class->ivars->ivar_list[i]; + ivar->offset += ivar_start; + /* If we're using the new ABI then we also set up the faster ivar + * offset variables. + */ + if (objc_test_class_flag(class, objc_class_flag_new_abi)) + { + *(class->ivar_offsets[i]) = ivar->offset; + } + } + } + } + else + { + if (NULL == class->ivars) { return; } + + Class super = class_getSuperclass(class); + int start = class->ivars->ivar_list[0].offset; + /* Quick and dirty test. If the first ivar comes straight after the last + * class, then it's fine. */ + if (Nil == super || start == super->instance_size) {return; } + + /* Find the last superclass with at least one ivar. */ + while (NULL == super->ivars) + { + super = class_getSuperclass(super); + } + struct objc_ivar *ivar = + &super->ivars->ivar_list[super->ivars->count-1]; + + // Find the end of the last ivar - instance_size contains some padding + // for alignment. + int real_end = ivar->offset + objc_sizeof_type(ivar->type); + // Keep going if the new class starts at the end of the superclass + if (start == real_end) + { + return; + } + // The classes don't line up, but don't panic; check that the + // difference is not just padding for alignment + int align = objc_alignof_type(class->ivars->ivar_list[0].type); + if (start > real_end && (start - align) < real_end) + { + return; + } + + /* Panic if this class has an instance variable that overlaps the + * superclass. */ + fprintf(stderr, + "Error: Instance variables in %s overlap superclass %s. ", + class->name, super->name); + fprintf(stderr, + "Offset of first instance variable, %s, is %d. ", + class->ivars->ivar_list[0].name, start); + fprintf(stderr, + "Last instance variable in superclass, %s, ends at offset %d. ", + ivar->name, ivar->offset + + (int)objc_sizeof_type(ivar->type)); + fprintf(stderr, "This probably means that you are subclassing a" + "class from a library, which has changed in a binary-incompatible" + "way.\n"); + abort(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Public API functions +//////////////////////////////////////////////////////////////////////////////// + +void object_setIvar(id object, Ivar ivar, id value) +{ + char *addr = (char*)object; + addr += ivar_getOffset(ivar); + *(id*)addr = value; +} + +Ivar object_setInstanceVariable(id obj, const char *name, void *value) +{ + Ivar ivar = class_getInstanceVariable(object_getClass(obj), name); + object_setIvar(obj, ivar, value); + return ivar; +} + +id object_getIvar(id object, Ivar ivar) +{ + return *(id*)(((char*)object) + ivar_getOffset(ivar)); +} + +Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) +{ + Ivar ivar = class_getInstanceVariable(object_getClass(obj), name); + if (NULL != outValue) + { + *outValue = object_getIvar(obj, ivar); + } + return ivar; +} diff --git a/third_party/libobjc/ivar.h b/third_party/libobjc/ivar.h new file mode 100644 index 0000000000000000000000000000000000000000..ea5a564b93fd90bed9e5fe8850bc2cd4944cb803 --- /dev/null +++ b/third_party/libobjc/ivar.h @@ -0,0 +1,47 @@ + +/** + * Metadata structure for an instance variable. + * + * Note: The modern Apple runtime apparently stores the alignment of the ivar + * here. We don't - we can compute it from the type, but it might be useful. + * + * It would also be good to add GC properties to this structure, and possibly + * an assignment policy (e.g. assign / retain / copy). + */ +struct objc_ivar +{ + /** + * Name of this instance variable. + */ + const char *name; + /** + * Type encoding for this instance variable. + */ + const char *type; + /** + * The offset from the start of the object. When using the non-fragile + * ABI, this is initialized by the compiler to the offset from the start of + * the ivars declared by this class. It is then set by the runtime to the + * offset from the object pointer. + */ + int offset; +}; + +/** + * A list of instance variables declared on this class. Unlike the method + * list, this is a single array and size. Categories are not allowed to add + * instance variables, because that would require existing objects to be + * reallocated, which is only possible with accurate GC (i.e. not in C). + */ +struct objc_ivar_list +{ + /** + * The number of instance variables in this list. + */ + int count; + /** + * An array of instance variable metadata structures. Note that this array + * has count elements. + */ + struct objc_ivar ivar_list[]; +}; diff --git a/third_party/libobjc/legacy_malloc.c b/third_party/libobjc/legacy_malloc.c new file mode 100644 index 0000000000000000000000000000000000000000..3eedf2736752852725cb18342842d300ec1b0442 --- /dev/null +++ b/third_party/libobjc/legacy_malloc.c @@ -0,0 +1,43 @@ +#include <stdlib.h> + +void *valloc(size_t); + +// Stubs that just call the libc implementations when you call these. + +void *objc_malloc(size_t size) +{ + return malloc(size); +} + +void *objc_atomic_malloc(size_t size) +{ + return malloc(size); +} + +#ifdef __MINGW32__ +void *objc_valloc(size_t size) +{ + return malloc(size); +} +#else +void *objc_valloc(size_t size) +{ + return valloc(size); +} +#endif + +void *objc_realloc(void *mem, size_t size) +{ + return realloc(mem, size); +} + +void * objc_calloc(size_t nelem, size_t size) +{ + return calloc(nelem, size); +} + +void objc_free(void *mem) +{ + free(mem); +} + diff --git a/third_party/libobjc/loader.c b/third_party/libobjc/loader.c new file mode 100644 index 0000000000000000000000000000000000000000..67a8cf5b95cd71957a4af862f84f1052774265cc --- /dev/null +++ b/third_party/libobjc/loader.c @@ -0,0 +1,114 @@ +#include <stdlib.h> +#include <assert.h> +#include "objc/runtime.h" +#include "lock.h" +#include "loader.h" +#include "visibility.h" +#ifdef ENABLE_GC +#include <gc/gc.h> +#endif +#include <stdio.h> + +/** + * Runtime lock. This is exposed in + */ +PRIVATE mutex_t runtime_mutex; +LEGACY void *__objc_runtime_mutex = &runtime_mutex; + +void init_alias_table(void); +void init_arc(void); +void init_class_tables(void); +void init_dispatch_tables(void); +void init_gc(void); +void init_protocol_table(void); +void init_selector_tables(void); +void init_trampolines(void); +void objc_send_load_message(Class class); + +/* Number of threads that are alive. */ +int __objc_runtime_threads_alive = 1; /* !T:MUTEX */ + +void __objc_exec_class(struct objc_module_abi_8 *module) +{ + static BOOL first_run = YES; + + // Check that this module uses an ABI version that we recognise. + // In future, we should pass the ABI version to the class / category load + // functions so that we can change various structures more easily. + assert(objc_check_abi_version(module)); + + if (first_run) + { +#if ENABLE_GC + init_gc(); +#endif + // Create the main runtime lock. This is not safe in theory, but in + // practice the first time that this function is called will be in the + // loader, from the main thread. Future loaders may run concurrently, + // but that is likely to break the semantics of a lot of languages, so + // we don't have to worry about it for a long time. + // + // The only case when this can potentially go badly wrong is when a + // pure-C main() function spawns two threads which then, concurrently, + // call dlopen() or equivalent, and the platform's implementation of + // this does not perform any synchronization. + INIT_LOCK(runtime_mutex); + // Create the various tables that the runtime needs. + init_selector_tables(); + init_protocol_table(); + init_class_tables(); + init_dispatch_tables(); + init_alias_table(); + init_arc(); + init_trampolines(); + first_run = NO; + } + + // The runtime mutex is held for the entire duration of a load. It does + // not need to be acquired or released in any of the called load functions. + LOCK_RUNTIME_FOR_SCOPE(); + + struct objc_symbol_table_abi_8 *symbols = module->symbol_table; + // Register all of the selectors used in this module. + if (symbols->selectors) + { + objc_register_selector_array(symbols->selectors, + symbols->selector_count); + } + + unsigned short defs = 0; + // Load the classes from this module + for (unsigned short i=0 ; i<symbols->class_count ; i++) + { + objc_load_class(symbols->definitions[defs++]); + } + unsigned int category_start = defs; + // Load the categories from this module + for (unsigned short i=0 ; i<symbols->category_count; i++) + { + objc_try_load_category(symbols->definitions[defs++]); + } + // Load the static instances + struct objc_static_instance_list **statics = (void*)symbols->definitions[defs]; + while (NULL != statics && NULL != *statics) + { + objc_init_statics(*(statics++)); + } + + // Load categories and statics that were deferred. + objc_load_buffered_categories(); + objc_init_buffered_statics(); + // Fix up the class links for loaded classes. + objc_resolve_class_links(); + for (unsigned short i=0 ; i<symbols->category_count; i++) + { + struct objc_category *cat = (struct objc_category*) + symbols->definitions[category_start++]; + Class class = (Class)objc_getClass(cat->class_name); + if ((Nil != class) && + objc_test_class_flag(class, objc_class_flag_resolved)) + { + objc_send_load_message(class); + } + } +} diff --git a/third_party/libobjc/loader.h b/third_party/libobjc/loader.h new file mode 100644 index 0000000000000000000000000000000000000000..a97063bdc067eda85dd8bf760c94734c909616d7 --- /dev/null +++ b/third_party/libobjc/loader.h @@ -0,0 +1,67 @@ +#ifndef __OBJC_LOADER_H_INCLUDED +#define __OBJC_LOADER_H_INCLUDED +#include "category.h" +#include "method_list.h" +#include "module.h" +#include "class.h" +#include "protocol.h" + +/** + * Checks whether it is safe to load a module with the specified version and + * module size. This depends on whether another module with an incompatible + * ABI has already been loaded. + */ +BOOL objc_check_abi_version(struct objc_module_abi_8 *module); +/** + * Initializes a protocol list, uniquing the protocols in the list. + */ +void objc_init_protocols(struct objc_protocol_list *protocols); +/** + * Registers a set of selectors from a method list. + */ +void objc_register_selectors_from_list(struct objc_method_list *l); +/** + * Register all of the (unregistered) selectors that are used in a class. + */ +void objc_register_selectors_from_class(Class class); +/** + * Registers all of the selectors in an array. + */ +void objc_register_selector_array(SEL selectors, unsigned long count); +/** + * Loads a class into the runtime system. If possible, the class is resolved + * (inserted into the class tree) immediately. If its superclass is not yet + * resolved, it is enqueued for later resolution. + */ +void objc_load_class(struct objc_class *class); +/** + * Resolves classes that have not yet been resolved, if their superclasses have + * subsequently been loaded. + */ +void objc_resolve_class_links(void); +/** + * Attaches a category to its class, if the class is already loaded. Buffers + * it for future resolution if not. + */ +void objc_try_load_category(struct objc_category *cat); +/** + * Tries to load all of the categories that could not previously be loaded + * because their classes were not yet loaded. + */ +void objc_load_buffered_categories(void); +/** + * Updates the dispatch table for a class. + */ +void objc_update_dtable_for_class(Class cls); +/** + * Initialises a list of static object instances belonging to the same class if + * possible, or defers initialisation until the class has been loaded it not. + */ +void objc_init_statics(struct objc_static_instance_list *statics); +/** + * Tries again to initialise static instances which could not be initialised + * earlier. + */ +void objc_init_buffered_statics(void); + +#endif //__OBJC_LOADER_H_INCLUDED diff --git a/third_party/libobjc/lock.h b/third_party/libobjc/lock.h new file mode 100644 index 0000000000000000000000000000000000000000..527c8714f845b968ef8bdb5ecbc346baa08b3ce2 --- /dev/null +++ b/third_party/libobjc/lock.h @@ -0,0 +1,70 @@ +/** + * libobjc requires recursive mutexes. These are delegated to the underlying + * threading implementation. This file contains a VERY thin wrapper over the + * Windows and POSIX mutex APIs. + */ + +#ifndef __LIBOBJC_LOCK_H_INCLUDED__ +#define __LIBOBJC_LOCK_H_INCLUDED__ +#ifdef WIN32 +#define BOOL _WINBOOL +# include <windows.h> +#undef BOOL +typedef HANDLE mutex_t; +# define INIT_LOCK(x) x = CreateMutex(NULL, FALSE, NULL) +# define LOCK(x) WaitForSingleObject(*x, INFINITE) +# define UNLOCK(x) ReleaseMutex(*x) +# define DESTROY_LOCK(x) CloseHandle(*x) +#else + +# include <pthread.h> + +typedef pthread_mutex_t mutex_t; +// If this pthread implementation has a static initializer for recursive +// mutexes, use that, otherwise fall back to the portable version +# ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# define INIT_LOCK(x) x = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# elif defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) +# define INIT_LOCK(x) x = PTHREAD_RECURSIVE_MUTEX_INITIALIZER +# else +# define INIT_LOCK(x) init_recursive_mutex(&(x)) + +static inline void init_recursive_mutex(pthread_mutex_t *x) +{ + pthread_mutexattr_t recursiveAttributes; + pthread_mutexattr_init(&recursiveAttributes); + pthread_mutexattr_settype(&recursiveAttributes, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(x, &recursiveAttributes); + pthread_mutexattr_destroy(&recursiveAttributes); +} +# endif + +# define LOCK(x) pthread_mutex_lock(x) +# define UNLOCK(x) pthread_mutex_unlock(x) +# define DESTROY_LOCK(x) pthread_mutex_destroy(x) +#endif + +__attribute__((unused)) static void objc_release_lock(void *x) +{ + mutex_t *lock = *(mutex_t**)x; + UNLOCK(lock); +} +/** + * Acquires the lock and automatically releases it at the end of the current + * scope. + */ +#define LOCK_FOR_SCOPE(lock) \ + __attribute__((cleanup(objc_release_lock)))\ + __attribute__((unused)) mutex_t *lock_pointer = lock;\ + LOCK(lock) + +/** + * The global runtime mutex. + */ +extern mutex_t runtime_mutex; + +#define LOCK_RUNTIME() LOCK(&runtime_mutex) +#define UNLOCK_RUNTIME() UNLOCK(&runtime_mutex) +#define LOCK_RUNTIME_FOR_SCOPE() LOCK_FOR_SCOPE(&runtime_mutex) + +#endif // __LIBOBJC_LOCK_H_INCLUDED__ diff --git a/third_party/libobjc/method_list.h b/third_party/libobjc/method_list.h new file mode 100644 index 0000000000000000000000000000000000000000..5a839077801a71e764305fa83ef7aacae348220b --- /dev/null +++ b/third_party/libobjc/method_list.h @@ -0,0 +1,43 @@ +/** + * Metadata structure describing a method. + */ +struct objc_method +{ + /** + * Selector used to send messages to this method. The type encoding of + * this method should match the types field. + */ + SEL selector; + /** + * The type encoding for this selector. Used only for introspection, and + * only required because of the stupid selector handling in the old GNU + * runtime. In future, this field may be reused for something else. + */ + const char *types; + /** + * A pointer to the function implementing this method. + */ + IMP imp; +}; + +/** + * Method list. Each class or category defines a new one of these and they are + * all chained together in a linked list, with new ones inserted at the head. + * When constructing the dispatch table, methods in the start of the list are + * used in preference to ones at the end. + */ +struct objc_method_list +{ + /** + * The next group of methods in the list. + */ + struct objc_method_list *next; + /** + * The number of methods in this list. + */ + int count; + /** + * An array of methods. Note that the actual size of this is count. + */ + struct objc_method methods[]; +}; diff --git a/third_party/libobjc/module.h b/third_party/libobjc/module.h new file mode 100644 index 0000000000000000000000000000000000000000..96e0a773c49f95a247289d9347d510eaf67a0018 --- /dev/null +++ b/third_party/libobjc/module.h @@ -0,0 +1,102 @@ +/** + * Defines the module structures. + * + * When defining a new ABI, the + */ + +/** + * The symbol table for a module. This structure references all of the + * Objective-C symbols defined for a module, allowing the runtime to find and + * register them. + */ +struct objc_symbol_table_abi_8 +{ + /** + * The number of selectors referenced in this module. + */ + unsigned long selector_count; + /** + * An array of selectors used in this compilation unit. SEL is a pointer + * type and this points to the first element in an array of selectors. + */ + SEL selectors; + /** + * The number of classes defined in this module. + */ + unsigned short class_count; + /** + * The number of categories defined in this module. + */ + unsigned short category_count; + /** + * A null-terminated array of pointers to symbols defined in this module. + * This contains class_count pointers to class structures, category_count + * pointers to category structures, and then zero or more pointers to + * static object instances. + * + * Current compilers only use this for constant strings. The runtime + * permits other types. + */ + void *definitions[]; +}; + +/** + * The module structure is passed to the __objc_exec_class function by a + * constructor function when the module is loaded. + * + * When defining a new ABI version, the first two fields in this structure must + * be retained. + */ +struct objc_module_abi_8 +{ + /** + * The version of the ABI used by this module. This is checked against the + * list of ABIs that the runtime supports, and the list of incompatible + * ABIs. + */ + unsigned long version; + /** + * The size of the module. This is used for sanity checking, to ensure + * that the compiler and runtime's idea of the module size match. + */ + unsigned long size; + /** + * The full path name of the source for this module. Not currently used + * for anything, could be used for debugging in theory, but duplicates + * information available from DWARF data, so probably won't. + */ + const char *name; + /** + * A pointer to the symbol table for this compilation unit. + */ + struct objc_symbol_table_abi_8 *symbol_table; +}; + +struct objc_module_abi_10 +{ + /** + * Inherited fields from version 8 of the ABI. + */ + struct objc_module_abi_8 old; + /** + * GC mode. GC_Optional code can be mixed with anything, but GC_None code + * can't be mixed with GC_Required code. + */ + int gc_mode; +}; + +/** + * List of static instances of a named class provided in this module. + */ +struct objc_static_instance_list +{ + /** + * The name of the class. The isa pointer of all of the instances will be + * set to the class with this name. + */ + char *class_name; + /** + * NULL-terminated array of statically-allocated instances. + */ + id instances[]; +}; diff --git a/third_party/libobjc/mutation.m b/third_party/libobjc/mutation.m new file mode 100644 index 0000000000000000000000000000000000000000..867cbb704b4bb948c13a8f1a4ec0eefcd926e3da --- /dev/null +++ b/third_party/libobjc/mutation.m @@ -0,0 +1,12 @@ +#include <stdio.h> +#include <stdlib.h> +#include "objc/runtime.h" + +// This function is exported as a weak symbol to enable GNUstep or some other +// framework to replace it trivially +void __attribute__((weak)) objc_enumerationMutation(id obj) +{ + fprintf(stderr, "Mutation occured during enumeration."); + abort(); +} + diff --git a/third_party/libobjc/nsobject.h b/third_party/libobjc/nsobject.h new file mode 100644 index 0000000000000000000000000000000000000000..7703a225e96634d9633b0737c2a333d3e27f8ef2 --- /dev/null +++ b/third_party/libobjc/nsobject.h @@ -0,0 +1,10 @@ +/** + * Stub declaration of NSObject. Lots of things in the runtime require the + */ +@interface NSObject +-retain; +-copy; +-(void)release; +-autorelease; +-(void)dealloc; +@end diff --git a/third_party/libobjc/objc/Availability.h b/third_party/libobjc/objc/Availability.h new file mode 100644 index 0000000000000000000000000000000000000000..408b9988ca42a017b6f9026803a2e3107db2291e --- /dev/null +++ b/third_party/libobjc/objc/Availability.h @@ -0,0 +1,20 @@ + +#ifdef STRICT_MACOS_X +# define OBJC_NONPORTABLE __attribute__((error("Function not supported by the Apple runtime"))) +#else +# define OBJC_NONPORTABLE +#endif + +#if !defined(__DEPRECATE_DIRECT_ACCESS) || defined(__OBJC_LEGACY_GNU_MODE__) || defined(__OBJC_RUNTIME_INTERNAL__) +# define OBJC_DEPRECATED +#else +# define OBJC_DEPRECATED __attribute__((deprecated)) +#endif + +#ifdef ERROR_UNSUPPORTED_RUNTIME_FUNCTIONS +# define OBJC_GNUSTEP_RUNTIME_UNSUPPORTED(x) \ + __attribute__((error(x " not supported by this runtime"))) +#else +# define OBJC_GNUSTEP_RUNTIME_UNSUPPORTED(x) +#endif + diff --git a/third_party/libobjc/objc/Object.h b/third_party/libobjc/objc/Object.h new file mode 100644 index 0000000000000000000000000000000000000000..e5c9c4669d83e78b575bf8b1470632e333db37d0 --- /dev/null +++ b/third_party/libobjc/objc/Object.h @@ -0,0 +1,7 @@ +#include <objc/runtime.h> + +@interface Object +{ + Class isa; +} +@end diff --git a/third_party/libobjc/objc/Protocol.h b/third_party/libobjc/objc/Protocol.h new file mode 100644 index 0000000000000000000000000000000000000000..d38c1f51d3be072be68c1d4ac747f173c3932a47 --- /dev/null +++ b/third_party/libobjc/objc/Protocol.h @@ -0,0 +1,3 @@ +#import "Object.h" + +@interface Protocol : Object @end diff --git a/third_party/libobjc/objc/blocks_private.h b/third_party/libobjc/objc/blocks_private.h new file mode 100644 index 0000000000000000000000000000000000000000..5f1fca15586461d5c6478576082d7b17b5bb5432 --- /dev/null +++ b/third_party/libobjc/objc/blocks_private.h @@ -0,0 +1,90 @@ +#ifndef __LIBOBJC_BLOCKS_PRIVATE_H_INCLUDED__ +#define __LIBOBJC_BLOCKS_PRIVATE_H_INCLUDED__ + +/* + * This header file exposes some implementation details of the blocks runtime + * that are needed, e.g., by libdispatch. + */ + + +/** + * Block descriptor that contains copy and dispose operations. + */ +struct Block_descriptor +{ + /** + * Reserved for future use. Currently always 0. + */ + unsigned long int reserved; + /** Size of the block. */ + unsigned long int size; + /** + * Copy function, generated by the compiler to help copy the block if it + * contains nontrivial copy operations. + */ + void (*copy_helper)(void *dst, void *src); + /** + * Dispose function, generated by the compiler to help copy the block if it + * contains nontrivial destructors. + */ + void (*dispose_helper)(void *src); + /** + * Objective-C type encoding of the block. + */ + const char *encoding; +}; + +// Helper structure +struct Block_layout +{ + /** + * Class pointer. Always initialised to &_NSConcreteStackBlock for blocks + * that are created on the stack or &_NSConcreteGlobalBlock for blocks that + * are created in global storage. + */ + void *isa; + /** + * Flags. See the block_flags enumerated type for possible values. + */ + int flags; + /** + * Reserved - always initialised to 0 by the compiler. Used for the + * reference count in this implementation. + */ + int reserved; + /** + * The function that implements the block. The first argument is this + * structure, the subsequent arguments are the block's explicit parameters. + * If the BLOCK_USE_SRET flag is set, there is an additional hidden + * argument, which is a pointer to the space on the stack allocated to hold + * the return value. + */ + void (*invoke)(void *, ...); + /** + * The block's descriptor. This is either Block_descriptor_basic or + * Block_descriptor, depending on whether the + * BLOCK_HAS_COPY_DISPOSE flag is set. + */ + struct Block_descriptor *descriptor; + /** + * Block variables are appended to this structure. + */ +}; + + + +#ifndef __OBJC_RUNTIME_INTERNAL__ +/* + * Deprecated Block_basic datastructure needed by libdispatch + */ +struct Block_basic { + void *isa; + int Block_flags; + int Block_size; + void (*Block_invoke)(void *); + void (*Block_copy)(void *dst, void *src); + void (*Block_dispose)(void *); +}; +#endif // __OBJC_RUNTIME_INTERNAL__ +#endif //__LIBOBJC_BLOCKS_PRIVATE_H_INCLUDED__ + diff --git a/third_party/libobjc/objc/blocks_runtime.h b/third_party/libobjc/objc/blocks_runtime.h new file mode 100644 index 0000000000000000000000000000000000000000..8a64bf2fd5e36114ff45e63e464792d213abef09 --- /dev/null +++ b/third_party/libobjc/objc/blocks_runtime.h @@ -0,0 +1,17 @@ +/* + * Blocks Runtime + */ + +#include "Availability.h" +#ifdef __cplusplus +#define BLOCKS_EXPORT extern "C" +#else +#define BLOCKS_EXPORT extern +#endif + +BLOCKS_EXPORT void *_Block_copy(void *); +BLOCKS_EXPORT void _Block_release(void *); +BLOCKS_EXPORT const char *block_getType_np(void *b) OBJC_NONPORTABLE; + +#define Block_copy(x) ((__typeof(x))_Block_copy((void *)(x))) +#define Block_release(x) _Block_release((void *)(x)) diff --git a/third_party/libobjc/objc/capabilities.h b/third_party/libobjc/objc/capabilities.h new file mode 100644 index 0000000000000000000000000000000000000000..950749c0805b37ee8840563aea34932493398afc --- /dev/null +++ b/third_party/libobjc/objc/capabilities.h @@ -0,0 +1,136 @@ +/** + * capabilities.h - This file defines the list of capabilities. Runtime + * capabilities can be checked. You may use #ifdef to test at compile time + * whether the runtime on the current platform understands the capability. + * This does not mean that the runtime implements the capability, however. + * + * A copy of this file exists for compatibility in GNUstep's Objective-C + * framework. When using this framework in conjunction with the GNU + * Objective-C runtime, most of the features will not be supported at run time, + * even if the corresponding macros are available at compile time. + * Additionally, several are compile-time options in the GNUstep runtime, so + * although they are present in the header and understood by the runtime, they + * may not be supported by the installed runtime. + */ +#include "Availability.h" + +#ifndef __GNUSTEP_CAPABILITIES_H__ +# define __GNUSTEP_CAPABILITIES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The runtime supports zero-cost exceptions. + */ +#define OBJC_CAP_EXCEPTIONS 0 +/** + * The runtime supports the @synchronize directive. + */ +#define OBJC_CAP_SYNCRONIZE 1 +/** + * The runtime supports property accessors. + */ +#define OBJC_CAP_PROPERTIES 2 +/** + * The runtime supports introspection on declared properties. + */ +#define OBJC_CAP_PROPERTY_INTROSPECTION 3 +/** + * The runtime supports optional methods and declared properties in protocols. + */ +#define OBJC_CAP_OPTIONAL_PROTOCOLS 4 +/** + * The runtime supports non-fragile instance variables. + */ +#define OBJC_CAP_NONFRAGILE_IVARS 5 +/** + * The runtime supports making method lookup dependent on the types, as well as + * the name, of the selector. + */ +#define OBJC_CAP_TYPE_DEPENDENT_DISPATCH 6 +/** + * The runtime was compiled in the low-memory profile. This trades some speed + * for reduced memory consumption. + */ +#define OBJC_CAP_LOW_MEMORY 7 +/** + * The runtime supports developer mode. When in user mode (the default), + * loading two classes with the same name will cause the program to abort. In + * developer mode, the new class will replace the old one. If the ivar layouts + * are the same, the new class will be treated as a category. If they are + * different, then it will replace the old one in the class table, meaning that + * message sends to the class will go to the new version, but existing + * instances will not acquire the new methods. + */ +#define OBJC_DEVELOPER_MODE 8 +/** + * This runtime supports the unified exception model. This means that + * Objective-C objects can be caught by either Objective-C or C++ exception + * handlers (the latter only in Objective-C++ code), irrespective of whether + * they are thrown from C++ throw of Objective-C @throw statements. + */ +#define OBJC_UNIFIED_EXCEPTION_MODEL 9 + +/** + * The runtime provides a hook that allows the compiler to register class + * aliases declared with the @compatibility_alias keyword. This allows the + * runtime to resolve the alias, e.g. if objc_getClass() is called with an + * alias as the argument. + */ +#define OBJC_CAP_REGISTERED_COMPATIBILITY_ALIASES 10 +/** + * The runtime supports automatic reference counting, including support for + * __weak references. + */ +#define OBJC_CAP_ARC 11 +/** + * The runtime has support for garbage collection, as introduced by OS X 10.5. + * This includes implementations of a set of write barrier functions. + */ +#define OBJC_CAP_GARBAGE_COLLECTION 12 +/** + * The runtime has support for associated references, as introduced with OS X + * 10.6. The objc_setAssociatedObject() and objc_getAssociatedObject() + * functions are available. + */ +#define OBJC_CAP_ASSOCIATED_REFERENCES 13 +/** + * The runtime supports storing objects in pointers. + */ +#define OBJC_CAP_SMALL_OBJECTS 14 +/** + * The runtime supports prototype-based object orientation. + */ +#define OBJC_CAP_PROTOTYPES 15 +/** + * The runtime provides APIs for debugging ARC-managed autorelease pools. + */ +#define OBJC_ARC_AUTORELEASE_DEBUG 16 + +/** + * Macro used to require the existence of a specific capability. This creates + * a function that is called by the loader and tests that the runtime supports + * the required capability, aborting if it does not. + */ +#define OBJC_REQUIRE_CAPABILITY(x) \ + __attribute__((constructor)) static void objc_test ## x(void)\ + {\ + if (!objc_test_capability(x))\ + {\ + fprintf(stderr, "Runtime does not support required feature: " #x "\n");\ + exit(1);\ + }\ + } + +/** + * Run time feature test. This function returns 1 if the runtime supports the + * specified feature or 0 if it does not. + */ +int objc_test_capability(int x) OBJC_NONPORTABLE; +#ifdef __cplusplus +} +#endif + +#endif //__GNUSTEP_CAPABILITIES_H__ diff --git a/third_party/libobjc/objc/developer.h b/third_party/libobjc/objc/developer.h new file mode 100644 index 0000000000000000000000000000000000000000..2752cf12d5fd0d31bfcca4b45b3c9999086b2142 --- /dev/null +++ b/third_party/libobjc/objc/developer.h @@ -0,0 +1,21 @@ +enum objc_developer_mode_np +{ + /** User mode - the default. */ + objc_developer_mode_user, + /** Developer mode - allows replacing classes. */ + objc_developer_mode_developer +}; +/* + * Sets the developer mode. When in user mode (the default), + * loading two classes with the same name will cause the program to abort. In + * developer mode, the new class will replace the old one. If the ivar layouts + * are the same, the new class will be treated as a category. If they are + * different, then it will replace the old one in the class table, meaning that + * message sends to the class will go to the new version, but existing + * instances will not acquire the new methods. + * + * The runtime currently only supports two modes, although more may be added in + * the future. The behaviour of the existing modes will be maintained if this + * is the case. + */ +void objc_setDeveloperMode_np(enum objc_developer_mode_np); diff --git a/third_party/libobjc/objc/encoding.h b/third_party/libobjc/objc/encoding.h new file mode 100644 index 0000000000000000000000000000000000000000..bb58c0b8c4b40a4f5eed60acbf8990c2ea1320db --- /dev/null +++ b/third_party/libobjc/objc/encoding.h @@ -0,0 +1,70 @@ +#ifndef __LIBOBJC_ENCODING_H_INCLUDED__ +#define __LIBOBJC_ENCODING_H_INCLUDED__ + +const char *objc_skip_type_qualifiers (const char *type); + +const char *objc_skip_typespec(const char *type); + +const char *objc_skip_argspec(const char *type); + + +size_t objc_sizeof_type(const char *type); + +size_t objc_alignof_type(const char *type); + +size_t objc_aligned_size(const char *type); + +size_t objc_promoted_size(const char *type); + +void method_getReturnType(Method method, char *dst, size_t dst_len); + +const char *method_getTypeEncoding(Method method); + +void method_getArgumentType(Method method, + unsigned int index, + char *dst, + size_t dst_len); + +unsigned method_getNumberOfArguments(Method method); + +unsigned method_get_number_of_arguments(struct objc_method *method); + +char * method_copyArgumentType(Method method, unsigned int index); + +char * method_copyReturnType(Method method); + +//////////////////////////////////////////////////////////////////////////////// +// Deprecated functions - do not use functions below this line in new code. +//////////////////////////////////////////////////////////////////////////////// +unsigned objc_get_type_qualifiers (const char *type); + +struct objc_struct_layout +{ + const char *original_type; + const char *type; + const char *prev_type; + unsigned int record_size; + unsigned int record_align; +}; + +// Note: The implementations of these functions is horrible. +void objc_layout_structure (const char *type, + struct objc_struct_layout *layout); + +BOOL objc_layout_structure_next_member(struct objc_struct_layout *layout); + +void objc_layout_structure_get_info (struct objc_struct_layout *layout, + unsigned int *offset, + unsigned int *align, + const char **type); + +#define _F_CONST 0x01 +#define _F_IN 0x01 +#define _F_OUT 0x02 +#define _F_INOUT 0x03 +#define _F_BYCOPY 0x04 +#define _F_BYREF 0x08 +#define _F_ONEWAY 0x10 +#define _F_GCINVISIBLE 0x20 + +#endif // __LIBOBJC_ENCODING_H_INCLUDED__ diff --git a/third_party/libobjc/objc/hooks.h b/third_party/libobjc/objc/hooks.h new file mode 100644 index 0000000000000000000000000000000000000000..7fdcf88091311cb91254b863b54739843b570e68 --- /dev/null +++ b/third_party/libobjc/objc/hooks.h @@ -0,0 +1,74 @@ +/** + * This file includes all of the hooks that can be used to alter the behaviour + * of the runtime. + */ + + +#ifndef OBJC_HOOK +#define OBJC_HOOK extern +#endif +struct objc_category; +/** + * Class lookup hook. Set this to provide a mechanism for resolving classes + * that have not been registered with the runtime. This can be used for lazy + * library loading, for example. The hook takes a class name as an argument + * and returns the class. A JIT compiler could use this to allow classes to be + * compiled the first time that they are looked up. If the class is already + * registered with the runtime, this will not be called, so it can not be used + * for lazy loading of categories. + */ +OBJC_HOOK Class (*_objc_lookup_class)(const char *name); +/** + * Class load callback. + */ +OBJC_HOOK void (*_objc_load_callback)(Class cls, struct objc_category *category); +/** + * The hook used for fast proxy lookups. This takes an object and a selector + * and returns the instance that the message should be forwarded to. + */ +extern id (*objc_proxy_lookup)(id receiver, SEL op); +/** + * New runtime forwarding hook. This might be removed in future - it's + * actually no more expressive than the forward2 hook and forces Foundation to + * do some stuff that the runtime is better suited to. + */ +extern struct objc_slot *(*__objc_msg_forward3)(id, SEL); +/** + * Forwarding hook. Takes an object and a selector and returns a method that + * handles the forwarding. + */ +OBJC_HOOK IMP (*__objc_msg_forward2)(id, SEL); +/** + * Hook defined for handling unhandled exceptions. If the unwind library + * reaches the end of the stack without finding a handler then this hook is + * called. + */ +OBJC_HOOK void (*_objc_unexpected_exception)(id exception); +/** + * Hook defined to return the class to be used for boxing a foreign exception + * type. The class must implement: + * + * + (id)exceptionWithForeignException: (_Unwind_Exception*)ex; + * + * This will return an instance of the class that encapsulates the exception. + * + * Note: Due to limitations of the current ABI, there is no way for the handler + * to + */ +OBJC_HOOK Class (*_objc_class_for_boxing_foreign_exception)(int64_t exceptionClass); + +/** + * Hook called when selector type does not match the method type in the + * receiver. This should return the slot to use instead, although it may throw + * an exception or perform some other action. + */ +extern struct objc_slot* (*_objc_selector_type_mismatch)(Class cls, + SEL selector, struct objc_slot *result); + +/** + * Returns the object if it is not currently in the process of being + * deallocated. Returns nil otherwise. + * + * This hook must be set for weak references to work with automatic reference counting. + */ +OBJC_HOOK id (*_objc_weak_load)(id object); diff --git a/third_party/libobjc/objc/objc-api.h b/third_party/libobjc/objc/objc-api.h new file mode 100644 index 0000000000000000000000000000000000000000..9786447367c1e22ff6c064c4258b874f3eb17baa --- /dev/null +++ b/third_party/libobjc/objc/objc-api.h @@ -0,0 +1 @@ +#include <objc/runtime.h> diff --git a/third_party/libobjc/objc/objc-arc.h b/third_party/libobjc/objc/objc-arc.h new file mode 100644 index 0000000000000000000000000000000000000000..c7a420038de85d04c69be51d298bd883c41eb668 --- /dev/null +++ b/third_party/libobjc/objc/objc-arc.h @@ -0,0 +1,106 @@ +#ifndef __OBJC_ARC_INCLUDED__ +#define __OBJC_ARC_INCLUDED__ +/** + * Autoreleases the argument. Equivalent to [obj autorelease]. + */ +id objc_autorelease(id obj); +/** + * Autoreleases a return value. This is equivalent to [obj autorelease], but + * may also store the object somewhere where it can be quickly removed without + * the need for any message sending. + */ +id objc_autoreleaseReturnValue(id obj); +/** + * Initializes object as a weak pointer and stores value in it, or nil if value + * has already begun deallocation. + */ +id objc_initWeak(id *object, id value); +/** + * Loads the object. Returns nil if the object stored at this address has + * already begun deallocation. + */ +id objc_loadWeak(id* object); +/** + * Loads a weak value and retains it. + */ +id objc_loadWeakRetained(id* obj); +/** + * Retains the argument. Equivalent to [obj retain]. + */ +id objc_retain(id obj); +/** + * Retains and autoreleases an object. Equivalent to [[obj retain] autorelease]. + */ +id objc_retainAutorelease(id obj); +/** + * Retains and releases a return value. Equivalent to + * objc_retain(objc_autoreleaseReturnValue(obj)). + */ +id objc_retainAutoreleaseReturnValue(id obj); +/** + * Retains a return value that has previously been autoreleased and returned. + * This is equivalent to objc_retainAutoreleaseReturnValue(), but may support a + * fast path, skipping the autorelease pool entirely. + */ +id objc_retainAutoreleasedReturnValue(id obj); +/** + * Retains a block. + */ +id objc_retainBlock(id b); +/** + * Stores value in addr. This first retains value, then releases the old value + * at addr, and stores the retained value in the address. + */ +id objc_storeStrong(id *addr, id value); +/** + * Stores obj in zeroing weak pointer addr. If obj has begun deallocation, + * then this stores nil. + */ +id objc_storeWeak(id *addr, id obj); +/** + * Allocates an autorelease pool and pushes it onto the top of the autorelease + * pool stack. Note that the returned autorelease pool is not required to be + * an object. + */ +void *objc_autoreleasePoolPush(void); +/** + * Pops the specified autorelease pool from the stack, sending release messages + * to every object that has been autreleased since the pool was created. + */ +void objc_autoreleasePoolPop(void *pool); +/** + * Initializes dest as a weak pointer and stores the value stored in src into + * it. + */ +void objc_copyWeak(id *dest, id *src); +/** + * Destroys addr as a weak pointer. + */ +void objc_destroyWeak(id* addr); +/** + * Equivalent to objc_copyWeak(), but may also set src to nil. + */ +void objc_moveWeak(id *dest, id *src); +/** + * Releases an object. Equivalent to [obj release]. + */ +void objc_release(id obj); +/** + * Mark the object as about to begin deallocation. All subsequent reads of + * weak pointers will return 0. This function should be called in -release, + * before calling [self dealloc]. + * + * Nonstandard extension. + */ +void objc_delete_weak_refs(id obj); +/** + * Returns the total number of objects in the ARC-managed autorelease pool. + */ +unsigned long objc_arc_autorelease_count_np(void); +/** + * Returns the total number of times that an object has been autoreleased in + * this thread. + */ +unsigned long objc_arc_autorelease_count_for_object_np(id); +#endif // __OBJC_ARC_INCLUDED__ + diff --git a/third_party/libobjc/objc/objc-auto.h b/third_party/libobjc/objc/objc-auto.h new file mode 100644 index 0000000000000000000000000000000000000000..2ec4939574fc6108b7051e353e7f74ee4a9f6b3f --- /dev/null +++ b/third_party/libobjc/objc/objc-auto.h @@ -0,0 +1,255 @@ +/** + * objc-auto.h - This file provides the interface for Objective-C garbage + * collection + */ + +/** + * Flags passed to objc_collect. The low 2 bits specify the type of collection + * to perform, the remainder provide additional options. + */ +enum +{ + /** + * Perform an incremental collection if the collection ratio has not been + * exceeded, or a full collection if it has. + */ + OBJC_RATIO_COLLECTION = 0, + /** + * Performs an incremental collection. + */ + OBJC_GENERATIONAL_COLLECTION = 1, + /** + * Performs a full collection. + */ + OBJC_FULL_COLLECTION = 2, + /** + * Repeatedly performs a full collection until collection does not find any + * new free memory. + */ + OBJC_EXHAUSTIVE_COLLECTION = 3, + /** + * Only runs the collector (in any mode) if the number of bytes allocated + * since the last collection is greater than the threshold. + */ + OBJC_COLLECT_IF_NEEDED = (1 << 3), + /** + * Does not return until the collector has finished running. + */ + OBJC_WAIT_UNTIL_DONE = (1 << 4), +}; + +/** + * Options for objc_clear_stack(). + */ +enum +{ + /** Ignored - provided for OS X compatibility. */ + OBJC_CLEAR_RESIDENT_STACK = 1 +}; + + +/** + * Instructs the garbage collector to run. + */ +void objc_collect(unsigned long options); + +/** + * Returns yes if the connector is currently running, i.e. if every call to + * objc_gc_disable() has been balanced with a corresponding call to + * objc_gc_enable(). + */ +BOOL objc_collectingEnabled(void); + +/** + * Returns YES if running in GC mode, NO otherwise. + */ +BOOL objc_collecting_enabled(void); + +/** + * Starts concurrent collection. If this has been called, then finalizers will + * run in a separate thread. + */ +void objc_startCollectorThread(void); + +/** + * Causes all finalizers for instances of the specified class to be run on the + * main thread. This is currently unimplemented. + */ +void objc_finalizeOnMainThread(Class cls); + +/** + * Attempts to delete pointers currently stored on unused bits of the stack. + */ +void objc_clear_stack(unsigned long options); + +/** + * Returns YES if an object has been finalized. + */ +BOOL objc_is_finalized(void *ptr); + +/** + * Performs an atomic compare and exchange on a pointer value. Sets the value + * at objectLocation to replacement, if the current value is predicate. + */ +BOOL objc_atomicCompareAndSwapPtr(id predicate, + id replacement, + volatile id *objectLocation); +/** + * Performs an atomic compare and exchange on a pointer value. Sets the value + * at objectLocation to replacement, if the current value is predicate. + */ +BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, + id replacement, + volatile id *objectLocation); + +/** + * Performs an atomic compare and exchange on a pointer value. Sets the value + * at objectLocation to replacement, if the current value is predicate. + */ +BOOL objc_atomicCompareAndSwapGlobal(id predicate, + id replacement, + volatile id *objectLocation); +/** + * Performs an atomic compare and exchange on a pointer value. Sets the value + * at objectLocation to replacement, if the current value is predicate. + */ +BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, + id replacement, + volatile id *objectLocation); +/** + * Performs an atomic compare and exchange on a pointer value. Sets the value + * at objectLocation to replacement, if the current value is predicate. + */ +BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, + id replacement, + volatile id *objectLocation); +/** + * Performs an atomic compare and exchange on a pointer value. Sets the value + * at objectLocation to replacement, if the current value is predicate. + */ +BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, + id replacement, + volatile id *objectLocation); + +//////////////////////////////////////////////////////////////////////////////// +// The next group of functions are intended to be called automatically by the +// compiler. Normal user code will not call them. +//////////////////////////////////////////////////////////////////////////////// + +/** + * Performs a strong assignment. Stores val in *ptr, ensuring that the + * assignment is visible to the collector. + */ +id objc_assign_strongCast(id val, id *ptr); + +/** + * Assigns val to the global pointed to by ptr, ensuring that the assignment is + * visible to the collector. + */ +id objc_assign_global(id val, id *ptr); +/** + * Assigns val to the instance variable offset bytes from dest. + */ +id objc_assign_ivar(id val, id dest, ptrdiff_t offset); +/** + * Performs a memmove() operation, ensuring that the copied bytes are always + * visible to the collector. + */ +void *objc_memmove_collectable(void *dst, const void *src, size_t size); +/** + * Reads a weak pointer value. All reads of pointers declared __weak MUST be + * via this call. + */ +id objc_read_weak(id *location); +/** + * Assigns a value to location, which MUST have been declared __weak. All + * assignments to weak pointers must go via this function. + */ +id objc_assign_weak(id value, id *location); + + +/** + * Registers the current thread with the garbage collector. Should be done as + * soon as a thread is created. Until this is called, the thread's stack will + * be invisible to the collector. + */ +void objc_registerThreadWithCollector(void); +/** + * Unregisters the current thread. The thread's stack becomes invisible to the + * collector. This should be called just before the thread exits. + */ +void objc_unregisterThreadWithCollector(void); +/** + * Registers the current thread with the garbage collector and aborts if the + * registration failed. The thread is expected to have already been + * registered. This will print a warning message if it has not been. + */ +void objc_assertRegisteredThreadWithCollector(); + +//////////////////////////////////////////////////////////////////////////////// +// Functions below this line are extensions to the OS X GC API, intended to +// allow implementation of the higher-level public APIs +//////////////////////////////////////////////////////////////////////////////// + +/** + * Disables the garbage collector until it is reenabled with objc_gc_enable. + */ +void objc_gc_disable(void); +/** + * Enables the garbage collector, if it has been previously disabled with a + * call to objc_gc_disable(). These calls store an internal count. If + * objc_gc_disable() is called twice, then collection will not resume until + * objc_gc_enable() has also been called twice. + */ +void objc_gc_enable(void); + +/** + * Increments the reference count of objects. This is intended to be used to + * implement CFRetain(). Reference counts should only be used when storing + * pointers to objects in untracked allocations (e.g. malloc() memory). + * + * This function is intended to be used to implement CFRetain(). + */ +id objc_gc_retain(id object); +/** + * Decrements the reference count on an object. An object becomes eligible for + * automatic collection when its reference count reaches zero. New objects + * have a reference count of zero, so they are eligible for collection as soon + * as the last pointer to them vanishes. + * + * This function is intended to be used to implement CFRelease(). + */ +void objc_gc_release(id object); +/** + * Returns the retain count of an object. This is 0 for objects that have not + * had objc_gc_retain() called on them, which should be most objects in a + * garbage-collected program. + */ +int objc_gc_retain_count(id object); +/** + * Allocates a buffer of memory, which will be automatically deallocated by the + * collector. If isScanned is true, then this memory may contain pointers. If + * not, then pointers stored in the returned region will be ignored. + * + * This function is intended to be used to implement NSAllocateCollectable(). + */ +void* objc_gc_allocate_collectable(size_t size, BOOL isScanned); + +/** + * Reallocates a block of collectable memory. + */ +void* objc_gc_reallocate_collectable(void *ptr, size_t size, BOOL isScanned); + +/** + * If the pointer lies in a collectible memory region, returns the address at + * the start of this region. Otherwise, returns NULL. + */ +void* objc_gc_collectable_address(void* ptr); + +/** + * Registers a class that should be copied on assignment to heap locations. + * For performance reasons, the runtime only permits a small number of classes + * to be registered. These will always be copied when they are assigned, using + * the function specified in the second argument. + */ +BOOL objc_register_stack_class(Class cls, IMP copyFunction); diff --git a/third_party/libobjc/objc/objc.h b/third_party/libobjc/objc/objc.h new file mode 100644 index 0000000000000000000000000000000000000000..9786447367c1e22ff6c064c4258b874f3eb17baa --- /dev/null +++ b/third_party/libobjc/objc/objc.h @@ -0,0 +1 @@ +#include <objc/runtime.h> diff --git a/third_party/libobjc/objc/runtime-deprecated.h b/third_party/libobjc/objc/runtime-deprecated.h new file mode 100644 index 0000000000000000000000000000000000000000..f85d3df27a2f56639c78a9067b313594175c8bd4 --- /dev/null +++ b/third_party/libobjc/objc/runtime-deprecated.h @@ -0,0 +1,78 @@ +#if !defined(__GNUSTEP_LIBOBJC_RUNTIME_DEPRECATED_INCLUDED__) && !defined(GNUSTEP_LIBOBJC_NO_LEGACY) +# define __GNUSTEP_LIBOBJC_RUNTIME_DEPRECATED_INCLUDED__ + +/** + * Legacy GNU runtime compatibility. + * + * All of the functions in this section are deprecated and should not be used + * in new code. + */ + +__attribute__((deprecated)) +void *objc_malloc(size_t size); + +__attribute__((deprecated)) +void *objc_atomic_malloc(size_t size); + +__attribute__((deprecated)) +void *objc_valloc(size_t size); + +__attribute__((deprecated)) +void *objc_realloc(void *mem, size_t size); + +__attribute__((deprecated)) +void * objc_calloc(size_t nelem, size_t size); + +__attribute__((deprecated)) +void objc_free(void *mem); + +__attribute__((deprecated)) +id objc_get_class(const char *name); + +__attribute__((deprecated)) +id objc_lookup_class(const char *name); + +__attribute__((deprecated)) +id objc_get_meta_class(const char *name); + +#if !defined(__OBJC_RUNTIME_INTERNAL__) +__attribute__((deprecated)) +#endif +Class objc_next_class(void **enum_state); + +__attribute__((deprecated)) +Class class_pose_as(Class impostor, Class super_class); + +__attribute__((deprecated)) +SEL sel_get_typed_uid (const char *name, const char *types); + +__attribute__((deprecated)) +SEL sel_get_any_typed_uid (const char *name); + +__attribute__((deprecated)) +SEL sel_get_any_uid (const char *name); + +__attribute__((deprecated)) +SEL sel_get_uid(const char *name); + +__attribute__((deprecated)) +const char *sel_get_name(SEL selector); + +#if !defined(__OBJC_RUNTIME_INTERNAL__) +__attribute__((deprecated)) +#endif +BOOL sel_is_mapped(SEL selector); + +__attribute__((deprecated)) +const char *sel_get_type(SEL selector); + +__attribute__((deprecated)) +SEL sel_register_name(const char *name); + +__attribute__((deprecated)) +SEL sel_register_typed_name(const char *name, const char *type); + +__attribute__((deprecated)) +BOOL sel_eq(SEL s1, SEL s2); + +#endif diff --git a/third_party/libobjc/objc/runtime.h b/third_party/libobjc/objc/runtime.h new file mode 100644 index 0000000000000000000000000000000000000000..86c67ed5a2e1debe4246f2edc5286499d366f84f --- /dev/null +++ b/third_party/libobjc/objc/runtime.h @@ -0,0 +1,1040 @@ +#ifndef __LIBOBJC_RUNTIME_H_INCLUDED__ +#define __LIBOBJC_RUNTIME_H_INCLUDED__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __GNUSTEP_RUNTIME__ +# define __GNUSTEP_RUNTIME__ +#endif + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#ifndef __unsafe_unretained +# ifndef __has_feature +# define __unsafe_unretained +# elif !__has_feature(objc_arc) +# define __unsafe_unretained +# endif +#endif + +// Make sure we get the limit macros, even in C++ mode +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include <stdint.h> +#include <limits.h> +#include <stddef.h> +#include <sys/types.h> +#include "Availability.h" + +// Undo GNUstep substitutions +#ifdef class_setVersion +# undef class_setVersion +#endif +#ifdef class_getClassMethod +# undef class_getClassMethod +#endif +#ifdef objc_getClass +# undef objc_getClass +#endif +#ifdef objc_lookUpClass +# undef objc_lookUpClass +#endif + +/** + * Opaque type for Objective-C instance variable metadata. + */ +typedef struct objc_ivar* Ivar; + +// Don't redefine these types if the old GCC header was included first. +#ifndef __objc_INCLUDE_GNU +// Define the macro so that including the old GCC header does nothing. +# define __objc_INCLUDE_GNU +# define __objc_api_INCLUDE_GNU + + +/** + * Opaque type used for selectors. + */ +#if !defined(__clang__) && !defined(__OBJC_RUNTIME_INTERNAL__) +typedef const struct objc_selector *SEL; +#else +typedef struct objc_selector *SEL; +#endif + +/** + * Opaque type for Objective-C classes. + */ +typedef struct objc_class *Class; + +/** + * Type for Objective-C objects. + */ +typedef struct objc_object +{ + /** + * Pointer to this object's class. Accessing this directly is STRONGLY + * discouraged. You are recommended to use object_getClass() instead. + */ +#ifndef __OBJC_RUNTIME_INTERNAL__ + __attribute__((deprecated)) +#endif + Class isa; +} *id; + +/** + * Structure used for calling superclass methods. + */ +struct objc_super +{ + /** The receiver of the message. */ + __unsafe_unretained id receiver; + /** The class containing the method to call. */ +# if !defined(__cplusplus) && !__OBJC2__ + Class class; +# else + Class super_class; +# endif +}; + +/** + * Instance Method Pointer type. Note: Since the calling convention for + * variadic functions sometimes differs from the calling convention for + * non-variadic functions, you must cast an IMP to the correct type before + * calling. + */ +typedef id (*IMP)(id, SEL, ...); +/** + * Opaque type for Objective-C method metadata. + */ +typedef struct objc_method *Method; + +/** + * Objective-C boolean type. + */ +# ifdef STRICT_APPLE_COMPATIBILITY +typedef signed char BOOL; +# else +# ifdef __vxwords +typedef int BOOL; +# else +typedef unsigned char BOOL; +# endif +# endif + +#else +// Method in the GCC runtime is a struct, Method_t is the pointer +# define Method Method_t +#endif // __objc_INCLUDE_GNU + + +/** + * Opaque type for Objective-C property metadata. + */ +typedef struct objc_property* objc_property_t; +/** + * Opaque type for Objective-C protocols. Note that, although protocols are + * objects, sending messages to them is deprecated in Objective-C 2 and may not + * work in the future. + */ +#ifdef __OBJC__ +@class Protocol; +#else +typedef struct objc_protocol Protocol; +#endif + +/** + * Objective-C method description. + */ +struct objc_method_description +{ + /** + * The name of this method. + */ + SEL name; + /** + * The types of this method. + */ + const char *types; +}; + +/** + * The objc_property_attribute_t type is used to store attributes for + * properties. This is used to store a decomposed version of the property + * encoding, with each flag stored in the name and each value in the value. + * + * All of the strings that these refer to are internal to the runtime and + * should not be freed. + */ +typedef struct +{ + /** + * The flag that this attribute describes. All current flags are single characters, + */ + const char *name; + /** + */ + const char *value; +} objc_property_attribute_t; + + + +#ifndef YES +# define YES ((BOOL)1) +#endif +#ifndef NO +# define NO ((BOOL)0) +#endif + +#ifdef __GNUC +# define _OBJC_NULL_PTR __null +#elif defined(__cplusplus) +# define _OBJC_NULL_PTR 0 +#else +# define _OBJC_NULL_PTR ((void*)0) +#endif + +#ifndef nil +# define nil ((id)_OBJC_NULL_PTR) +#endif + +#ifndef Nil +# define Nil ((Class)_OBJC_NULL_PTR) +#endif + +#include "slot.h" + +/** + * Adds an instance variable to the named class. The class must not have been + * registered by the runtime. The alignment must be the base-2 logarithm of + * the alignment requirement and the types should be an Objective-C type encoding. + */ +BOOL class_addIvar(Class cls, + const char *name, + size_t size, + uint8_t alignment, + const char *types); + +/** + * Adds a method to the class. + */ +BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); + +/** + * Adds a protocol to the class. + */ +BOOL class_addProtocol(Class cls, Protocol *protocol); + +/** + * Tests for protocol conformance. Note: Currently, protocols with the same + * name are regarded as equivalent, even if they have different methods. This + * behaviour will change in a future version. + */ +BOOL class_conformsToProtocol(Class cls, Protocol *protocol); + +/** + * Copies the instance variable list for this class. The integer pointed to by + * the outCount argument is set to the number of instance variables returned. + * The caller is responsible for freeing the returned buffer. + */ +Ivar* class_copyIvarList(Class cls, unsigned int *outCount); + +/** + * Copies the method list for this class. The integer pointed to by the + * outCount argument is set to the number of methods returned. The caller is + * responsible for freeing the returned buffer. + */ +Method * class_copyMethodList(Class cls, unsigned int *outCount); + +/** + * Copies the declared property list for this class. The integer pointed to by + * the outCount argument is set to the number of declared properties returned. + * The caller is responsible for freeing the returned buffer. + */ +objc_property_t* class_copyPropertyList(Class cls, unsigned int *outCount); + +/** + * Copies the protocol list for this class. The integer pointed to by the + * outCount argument is set to the number of protocols returned. The caller is + * responsible for freeing the returned buffer. + */ +Protocol *__unsafe_unretained* class_copyProtocolList(Class cls, unsigned int *outCount); + +/** + * Creates an instance of this class, allocating memory using malloc. + */ +id class_createInstance(Class cls, size_t extraBytes); + +/** + * Returns a pointer to the method metadata for the specified method in this + * class. This is an opaque data type and must be accessed with the method_*() + * family of functions. + */ +Method class_getClassMethod(Class aClass, SEL aSelector); + +/** + * Returns a pointer to the metadata for the specified class variable in + * this class. This is an opaque data type and must be accessed with the + * ivar_*() family of functions. + */ +Ivar class_getClassVariable(Class cls, const char* name); + +/** + * Returns a pointer to the method metadata for the specified instance method + * in this class. This is an opaque data type and must be accessed with the + * method_*() family of functions. + */ +Method class_getInstanceMethod(Class aClass, SEL aSelector); + +/** + * Returns the size of an instance of the named class, in bytes. All of the + * class's superclasses must be loaded before this call, or the result is + * undefined with the non-fragile ABI. + */ +size_t class_getInstanceSize(Class cls); + +/** + * Look up the named instance variable in the class (and its superclasses) + * returning a pointer to the instance variable definition or a null + * pointer if no instance variable of that name was found. + */ +Ivar class_getInstanceVariable(Class cls, const char* name); + +/** + * Sets the object value of a specified instance variable. + */ +void object_setIvar(id object, Ivar ivar, id value); +/** + * Sets a named instance variable to the value specified by *value. Note that + * the instance variable must be a pointer-sized quantity. + */ +Ivar object_setInstanceVariable(id obj, const char *name, void *value); + +/** + * Returns the value of the named instance variable. This should not be used + * with instance variables that are not pointers. + */ +id object_getIvar(id object, Ivar ivar); + +/** + * Returns a named instance variable via the final parameter. Note that + * calling object_getIvar() on the value returned from this function is faster. + * + * Note that the instance variable must be a pointer-sized quantity. + */ +Ivar object_getInstanceVariable(id obj, const char *name, void **outValue); + +/** + * Returns a pointer to the function used to handle the specified message. If + * the receiver does not have a method corresponding to this message then this + * function may return a runtime function that performs forwarding. + */ +IMP class_getMethodImplementation(Class cls, SEL name); + +/** + * Identical to class_getMethodImplementation(). + */ +IMP class_getMethodImplementation_stret(Class cls, SEL name); + +/** + * Returns the name of the class. This string is owned by the runtime and is + * valid for (at least) as long as the class remains loaded. + */ +const char * class_getName(Class cls); + +/** + * Retrieves metadata about the property with the specified name. + */ +objc_property_t class_getProperty(Class cls, const char *name); + +/** + * Returns the superclass of the specified class. + */ +Class class_getSuperclass(Class cls); + +/** + * Returns the version of the class. Currently, the class version is not used + * inside the runtime at all, however it may be used for the developer-mode ABI. + */ +int class_getVersion(Class theClass); + +/** + * Sets the version for this class. + */ +void class_setVersion(Class theClass, int version); + +OBJC_GNUSTEP_RUNTIME_UNSUPPORTED("Weak instance variables") +const char *class_getWeakIvarLayout(Class cls); + +/** + * Returns whether the class is a metaclass. This can be used in conjunction + * with object_getClass() for differentiating between objects and classes. + */ +BOOL class_isMetaClass(Class cls); + +/** + * Registers an alias for the class. Returns YES if the alias could be + * registered successfully. + */ +OBJC_NONPORTABLE +BOOL class_registerAlias_np(Class cls, const char *alias); + +/** + * Replaces the named method with a new implementation. Note: the GNUstep + * Objective-C runtime uses typed selectors, however the types of the selector + * will be ignored and a new selector registered with the specified types. + */ +IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types); + +/** + * Returns YES if instances of this class has a method that implements the + * specified message, NO otherwise. If the class handles this message via one + * or more of the various forwarding mechanisms, then this will still return + * NO. + */ +BOOL class_respondsToSelector(Class cls, SEL sel); + +/** + * Returns the instance variable layout of this class as an opaque list that + * can be applied to other classes. + */ +const char *class_getIvarLayout(Class cls); +/** + * Sets the class's instance variable layout. The layout argument must be a + * value returned by class_getIvarLayout(). + */ +void class_setIvarLayout(Class cls, const char *layout); + +/** + * Sets the superclass of the specified class. This function is deprecated, + * because modifying the superclass of a class at run time is a very complex + * operation and this function is almost always used incorrectly. + */ +__attribute__((deprecated)) +Class class_setSuperclass(Class cls, Class newSuper); + +OBJC_GNUSTEP_RUNTIME_UNSUPPORTED("Weak instance variables") +void class_setWeakIvarLayout(Class cls, const char *layout); + +/** + * Returns the name of an instance variable. + */ +const char* ivar_getName(Ivar ivar); + +/** + * Returns the offset of an instance variable. This value can be added to the + * object pointer to get the address of the instance variable. + */ +ptrdiff_t ivar_getOffset(Ivar ivar); + +/** + * Returns the Objective-C type encoding of the instance variable. + */ +const char* ivar_getTypeEncoding(Ivar ivar); + +/** + * Copies the type encoding of an argument of this method. The caller is + * responsible for freeing the returned C string. Arguments 0 and 1 of any + * Objective-C method will be the self and _cmd parameters, so the returned + * value will be "@" and ":" respectively. + */ +char* method_copyArgumentType(Method method, unsigned int index); + +/** + * Copies the type encoding of an argument of this method. The caller is + * responsible for freeing the returned C string. + */ +char* method_copyReturnType(Method method); + +/** + * Exchanges the implementations of the two methods. Note: this call is very + * expensive on the GNUstep runtime and its use is discouraged. It is + * recommended that users call class_replaceMethod() instead. + */ +void method_exchangeImplementations(Method m1, Method m2); + +/** + * Copies the Objective-C type encoding of a specified method parameter into a + * buffer provided by the caller. This method does not provide any means for + * the caller to easily detect truncation, and will only NULL-terminate the + * output string if there is enough space for the argument type and the NULL + * terminator. Its use is therefore discouraged. + */ +void method_getArgumentType(Method method, unsigned int index, char *dst, size_t dst_len); + +/** + * Returns a pointer to the function used to implement this method. + */ +IMP method_getImplementation(Method method); + +/** + * Returns the selector used to identify this method. Note that, unlike the + * Apple runtimes, the GNUstep runtime uses typed selectors, so the return + * value for this also identifies the type of the method, not just its name, + * although calling method_getTypeEncoding() is faster if you just require the + * types. + */ +SEL method_getName(Method method); + +/** + * Returns the number of arguments (including self and _cmd) that this method + * expects. + */ +unsigned method_getNumberOfArguments(Method method); + +/** + * Copies the Objective-C type encoding of a method's return value into a + * buffer provided by the caller. This method does not provide any means for + * the caller to easily detect truncation, and will only NULL-terminate the + * output string if there is enough space for the argument type and the NULL + * terminator. Its use is therefore discouraged. + */ +void method_getReturnType(Method method, char *dst, size_t dst_len); + +/** + * Returns the type encoding for the method. This string is owned by the + * runtime and will persist for (at least) as long as the class owning the + * method is loaded. + */ +const char * method_getTypeEncoding(Method method); + +/** + * Sets the function used to implement this method. This function is very + * expensive with the GNUstep runtime and its use is discouraged. It is + * recommended that you call class_replaceMethod() instead. + */ +IMP method_setImplementation(Method method, IMP imp); + +/** + * Allocates a new class and metaclass inheriting from the specified class, + * with some space after the class for storing extra data. This space can be + * used for class variables by adding instance variables to the returned + * metaclass. + */ +Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes); + +/** + * Frees a class and metaclass allocated with objc_allocateClassPair(). Any + * attempts to send messages to instances of this class or its subclasses + * result in undefined behaviour. + */ +void objc_disposeClassPair(Class cls); + +/** + * Returns the class with the specified name, if one has been registered with + * the runtime, or nil if one does not exist. If no class of this name is + * loaded, it calls the _objc_lookup_class() callback to allow an external + * library to load the module providing this class. + */ +id objc_getClass(const char *name); + +/** + * Copies all of the classes currently registered with the runtime into the + * buffer specified as the first argument. If the buffer is NULL or its length + * is 0, it returns the total number of classes registered with the runtime. + * Otherwise, it copies classes and returns the number copied. + */ +int objc_getClassList(Class *buffer, int bufferLen); +/** + * Returns a copy of the list of all classes in the system. The caller is + * responsible for freeing this list. The number of classes is returned in the + * parameter. + */ +Class *objc_copyClassList(unsigned int *outCount); + +/** + * Returns the metaclass with the specified name. This is equivalent to + * calling object_getClass() on the result of objc_getClass(). + */ +id objc_getMetaClass(const char *name); + +/** + * Returns the class with the specified name, aborting if none is found. This + * function should generally only be called early on in a program, to ensure + * that all required libraries are loaded. + */ +id objc_getRequiredClass(const char *name); + +/** + * Looks up the class with the specified name, but does not invoke any + * external lazy loading mechanisms. + */ +id objc_lookUpClass(const char *name); + +/** + * Returns the protocol with the specified name. + */ +Protocol *objc_getProtocol(const char *name); +/** + * Allocates a new protocol. This returns NULL if a protocol with the same + * name already exists in the system. + * + * Protocols are immutable after they have been registered, so may only be + * modified between calling this function and calling objc_registerProtocol(). + */ +Protocol *objc_allocateProtocol(const char *name); +/** + * Registers a protocol with the runtime. After this point, the protocol may + * not be modified. + */ +void objc_registerProtocol(Protocol *proto); +/** + * Adds a method to the protocol. + */ +void protocol_addMethodDescription(Protocol *aProtocol, + SEL name, + const char *types, + BOOL isRequiredMethod, + BOOL isInstanceMethod); +/** + * Adds a protocol to the protocol. + */ +void protocol_addProtocol(Protocol *aProtocol, Protocol *addition); +/** + * Adds a property to the protocol. + */ +void protocol_addProperty(Protocol *aProtocol, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount, + BOOL isRequiredProperty, + BOOL isInstanceProperty); + + +/** + * Registers a new class and its metaclass with the runtime. This function + * should be called after allocating a class with objc_allocateClassPair() and + * adding instance variables and methods to it. A class can not have instance + * variables added to it after objc_registerClassPair() has been called. + */ +void objc_registerClassPair(Class cls); + +/** + * Returns a pointer immediately after the instance variables declared in an + * object. This is a pointer to the storage specified with the extraBytes + * parameter given when allocating an object. + */ +void *object_getIndexedIvars(id obj); + +// FIXME: The GNU runtime has a version of this which omits the size parameter +//id object_copy(id obj, size_t size); + +/** + * Free an object created with class_createInstance(). + */ +id object_dispose(id obj); + +/** + * Returns the class of the object. Note: the isa pointer should not be + * accessed directly with the GNUstep runtime. + */ +Class object_getClass(id obj); + +/** + * Sets the class of the object. Note: the isa pointer should not be + * accessed directly with the GNUstep runtime. + */ +Class object_setClass(id obj, Class cls); + +/** + * Returns the name of the class of the object. This is equivalent to calling + * class_getName() on the result of object_getClass(). + */ +const char *object_getClassName(id obj); + + +/** + * Returns the name of a specified property. + */ +const char *property_getName(objc_property_t property); + +/** + * Returns the attributes for the specified property. This is similar to an + * Objective-C type encoding, but contains some extra information. A full + * description of the format for this string may be found in Apple's + * Objective-C Runtime Programming Guide. + */ +const char *property_getAttributes(objc_property_t property); + +/** + * Returns an array of attributes for this property. + */ +objc_property_attribute_t *property_copyAttributeList(objc_property_t property, + unsigned int *outCount); +/** + * Adds a property to the class, given a specified set of attributes. Note + * that this only sets the property metadata. The property accessor methods + * must already be created. + */ +BOOL class_addProperty(Class cls, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount); + +/** + * Replaces property metadata. If the property does not exist, then this is + * equivalent to calling class_addProperty(). + */ +void class_replaceProperty(Class cls, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount); + +/** + * Returns a copy of a single attribute. + */ +char *property_copyAttributeValue(objc_property_t property, + const char *attributeName); + +/** + * Testswhether a protocol conforms to another protocol. + */ +BOOL protocol_conformsToProtocol(Protocol *p, Protocol *other); + +/** + * Returns an array of method descriptions. Stores the number of elements in + * the array in the variable pointed to by the last parameter. The caller is + * responsible for freeing this array. + */ +struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, + BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *count); + +/** + * Returns an array of property metadata values, with the number being stored + * in the variable pointed to by the last argument. The caller is responsible + * for freeing the returned array. + */ +objc_property_t *protocol_copyPropertyList(Protocol *p, unsigned int *count); + +/** + * Returns an array of protocols that this protocol conforms to, with the + * number of protocols in the array being returned via the last argument. The + * caller is responsible for freeing this array. + */ +Protocol *__unsafe_unretained*protocol_copyProtocolList(Protocol *p, unsigned int *count); + +/** + * Returns all of the protocols that the runtime is aware of. Note that + * protocols compiled by GCC and not attacked to classes may not have been + * registered with the runtime. The number of protocols returned is stored at + * the address indicated by the pointer argument. + * + * The caller is responsible for freeing the returned array. + */ +Protocol *__unsafe_unretained*objc_copyProtocolList(unsigned int *outCount); +/** + * Returns the method description for the specified method within a given + * protocol. + */ +struct objc_method_description protocol_getMethodDescription(Protocol *p, + SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod); + +/** + * Returns the name of the specified protocol. + */ +const char* protocol_getName(Protocol *p); + +/** + * Returns the property metadata for the property with the specified name. + * + * Note: The Apple documentation for this method contains some nonsense for + * isInstanceProperty. As there is no language syntax for defining properties + * on classes, we return NULL if this is not YES. + */ +objc_property_t protocol_getProperty(Protocol *p, const char *name, + BOOL isRequiredProperty, BOOL isInstanceProperty); + +/** + * Compares two protocols. Currently, protocols are assumed to be equal if + * their names match. This is required for compatibility with the GCC ABI, + * which made not attempt to unique protocols (or even register them with the + * runtime). + */ +BOOL protocol_isEqual(Protocol *p, Protocol *other); + +/** + * The message lookup function used by the GCC ABI. This returns a pointer to + * the function (either a method or a forwarding hook) that should be called in + * response to a given message. + */ +IMP objc_msg_lookup(id, SEL) OBJC_NONPORTABLE; +/** + * The message lookup function used for messages sent to super in the GCC ABI. + * This specifies both the class and the + */ +IMP objc_msg_lookup_super(struct objc_super*, SEL) OBJC_NONPORTABLE; + +/** + * Returns the name of the specified selector. + */ +const char *sel_getName(SEL sel); + +/** + * Registers a selector with the runtime. This is equivalent to sel_registerName(). + */ +SEL sel_getUid(const char *selName); + +/** + * Returns whether two selectors are equal. For the purpose of comparison, + * selectors with the same name and type are regarded as equal. Selectors with + * the same name and different types are regarded as different. If one + * selector is typed and the other is untyped, but the names are the same, then + * they are regarded as equal. This means that sel_isEqual(a, b) and + * sel_isEqual(a, c) does not imply sel_isEqual(b, c) - if a is untyped but + * both b and c are typed selectors with different types, then then the first + * two will return YES, but the third case will return NO. + */ +BOOL sel_isEqual(SEL sel1, SEL sel2); + +/** + * Registers an untyped selector with the runtime. + */ +SEL sel_registerName(const char *selName); + +/** + * Register a typed selector. + */ +SEL sel_registerTypedName_np(const char *selName, const char *types) OBJC_NONPORTABLE; + +/** + * Returns the type encoding associated with a selector, or the empty string is + * there is no such type. + */ +const char *sel_getType_np(SEL aSel) OBJC_NONPORTABLE; + +/** + * Enumerates all of the type encodings associated with a given selector name + * (up to a specified limit). This function returns the number of types that + * exist for a specific selector, but only copies up to count of them into the + * array passed as the types argument. This allows you to call the function + * once with a relatively small on-stack buffer and then only call it again + * with a heap-allocated buffer if there is not enough space. + */ +unsigned sel_copyTypes_np(const char *selName, const char **types, unsigned count) OBJC_NONPORTABLE; + +/** + * Enumerates all of the type encodings associated with a given selector name + * (up to a specified limit). This function returns the number of types that + * exist for a specific selector, but only copies up to count of them into the + * array passed as the types argument. This allows you to call the function + * once with a relatively small on-stack buffer and then only call it again + * with a heap-allocated buffer if there is not enough space. + */ +unsigned sel_copyTypedSelectors_np(const char *selName, SEL *const sels, unsigned count) OBJC_NONPORTABLE; + +/** + * New ABI lookup function. Receiver may be modified during lookup or proxy + * forwarding and the sender may affect how lookup occurs. + */ +extern struct objc_slot *objc_msg_lookup_sender(id *receiver, SEL selector, id sender) + OBJC_NONPORTABLE; + +/** + * Registers a class for small objects. Small objects are stored inside a + * pointer. If the class can be registered, then this returns YES. The second + * argument specifies the bit pattern to use to identify the small object. + */ +BOOL objc_registerSmallObjectClass_np(Class cls, uintptr_t classId); + +/** + * The mask identifying the bits that can be used in an object pointer to + * identify a small object. On 32-bit systems, we use the low bit. On 64-bit + * systems, we use the low 3 bits. In both cases, the lowest bit must be 1. + * This restriction may be relaxed in the future on 64-bit systems. + */ +#ifndef UINTPTR_MAX +# define OBJC_SMALL_OBJECT_MASK ((sizeof(void*) == 4) ? 1 : 7) +#elif UINTPTR_MAX < UINT64_MAX +# define OBJC_SMALL_OBJECT_MASK 1 +#else +# define OBJC_SMALL_OBJECT_MASK 7 +#endif +/** + * The number of bits reserved for the class identifier in a small object. + */ +#ifndef UINTPTR_MAX +# define OBJC_SMALL_OBJECT_SHIFT ((sizeof(void*) == 4) ? 1 : 3) +#elif UINTPTR_MAX < UINT64_MAX +# define OBJC_SMALL_OBJECT_SHIFT 1 +#else +# define OBJC_SMALL_OBJECT_SHIFT 3 +#endif + + +/** + * Valid values for objc_AssociationPolicy. This is really a bitfield, but + * only specific combinations of flags are permitted. + */ +enum +{ + /** + * Perform straight assignment, no message sends. + */ + OBJC_ASSOCIATION_ASSIGN = 0, + /** + * Retain the associated object. + */ + OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, + /** + * Copy the associated object, by sending it a -copy message. + */ + OBJC_ASSOCIATION_COPY_NONATOMIC = 3, + /** + * Atomic retain. + */ + OBJC_ASSOCIATION_RETAIN = 0x301, + /** + * Atomic copy. + */ + OBJC_ASSOCIATION_COPY = 0x303 +}; +/** + * Association policy, used when setting associated objects. + */ +typedef uintptr_t objc_AssociationPolicy; + +/** + * Returns an object previously stored by calling objc_setAssociatedObject() + * with the same arguments, or nil if none exists. + */ +id objc_getAssociatedObject(id object, void *key); +/** + * Associates an object with another. This provides a mechanism for storing + * extra state with an object, beyond its declared instance variables. The + * pointer used as a key is treated as an opaque value. The best way of + * ensuring this is to pass the pointer to a static variable as the key. The + * value may be any object, but must respond to -copy or -retain, and -release, + * if an association policy of copy or retain is passed as the final argument. + */ +void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy); +/** + * Removes all associations from an object. + */ +void objc_removeAssociatedObjects(id object); + +/** + * Converts a block into an IMP that can be used as a method. The block should + * take an object pointer (self) as its first argument, and then the same + * arguments as the method. + */ +IMP imp_implementationWithBlock(void *block); +/** + * Returns the type encoding of an IMP that would be returned by passing the + * block to imp_implementationWithBlock(). Returns NULL if this is not a valid + * block encoding for transforming to an IMP (it must take id as its first + * argument). The caller is responsible for freeing the returned value. + */ +char *block_copyIMPTypeEncoding_np(void*block); +/** + * Returns the block that was used in an IMP created by + * imp_implementationWithBlock(). The result of calling this function with any + * other IMP is undefined. + */ +void *imp_getBlock(IMP anImp); +/** + * Removes a block that was converted to an IMP with + * imp_implementationWithBlock(). The result of calling this function with any + * other IMP is undefined. Returns YES on success, NO on failure. + */ +BOOL imp_removeBlock(IMP anImp); + +/** + * Adds a method to a specific object, This method will not be added to any + * other instances of the same class. + */ +BOOL object_addMethod_np(id object, SEL name, IMP imp, const char *types); + +/** + * Replaces a method on a specific object, This method will not be added to + * any other instances of the same class. + */ +IMP object_replaceMethod_np(id object, SEL name, IMP imp, const char *types); + +/** + * Creates a clone, in the JavaScript sense - an object which inherits both + * associated references and methods from the original object. + */ +id object_clone_np(id object); + +/** + * Returns the prototype of the object if it was created with + * object_clone_np(), or nil otherwise. + */ +id object_getPrototype_np(id object); + +/** + * Toggles whether Objective-C objects caught in C++ exception handlers in + * Objective-C++ mode should follow Objective-C or C++ semantics. The obvious + * choice is for them to follow C++ semantics, because people using a C++ + * language construct would intuitively expect them to have C++ semantics, + * where the catch behaviour depends on the static type of the thrown object, + * not its run-time type. + * + * Apple, therefore, chose the other option. + * + * We default to Apple-compatible mode, but can enable the sane behaviour if + * the user opts in. Note that doing this when linking against third-party + * frameworks written in Objective-C++ 2 may cause weird problems if the expect + * the other behaviour. + * + * This currently sets a global value. In the future, it may be configurable + * on a per-thread basis. + */ +int objc_set_apple_compatible_objcxx_exceptions(int newValue) OBJC_NONPORTABLE; + + +#define _C_ID '@' +#define _C_CLASS '#' +#define _C_SEL ':' +#define _C_BOOL 'B' + +#define _C_CHR 'c' +#define _C_UCHR 'C' +#define _C_SHT 's' +#define _C_USHT 'S' +#define _C_INT 'i' +#define _C_UINT 'I' +#define _C_LNG 'l' +#define _C_ULNG 'L' +#define _C_LNG_LNG 'q' +#define _C_ULNG_LNG 'Q' + +#define _C_FLT 'f' +#define _C_DBL 'd' + +#define _C_BFLD 'b' +#define _C_VOID 'v' +#define _C_UNDEF '?' +#define _C_PTR '^' + +#define _C_CHARPTR '*' +#define _C_ATOM '%' + +#define _C_ARY_B '[' +#define _C_ARY_E ']' +#define _C_UNION_B '(' +#define _C_UNION_E ')' +#define _C_STRUCT_B '{' +#define _C_STRUCT_E '}' +#define _C_VECTOR '!' + +#define _C_COMPLEX 'j' +#define _C_CONST 'r' +#define _C_IN 'n' +#define _C_INOUT 'N' +#define _C_OUT 'o' +#define _C_BYCOPY 'O' +#define _C_ONEWAY 'V' + +#include "runtime-deprecated.h" + +#ifdef __cplusplus +} +#endif + +#endif // __LIBOBJC_RUNTIME_H_INCLUDED__ diff --git a/third_party/libobjc/objc/slot.h b/third_party/libobjc/objc/slot.h new file mode 100644 index 0000000000000000000000000000000000000000..0afd7548adc464879e86acaf3705daf7b748efee --- /dev/null +++ b/third_party/libobjc/objc/slot.h @@ -0,0 +1,44 @@ +#ifndef __OBJC_SLOT_H_INCLUDED__ +#define __OBJC_SLOT_H_INCLUDED__ +/** + * The objc_slot structure is used to permit safe IMP caching. It is returned + * by the new lookup APIs. When you cache an IMP, you should store a copy of + * the version field and a pointer to the slot. + * + * The slot version is guaranteed never to be 0. When updating a cache, you + * should use code of the following form: + * + * 1) version = 0; + * 2) slot->cachedFor = receiver->isa; + * 3) slot_cache = slot; + * 4) version = slot->version; + * + * The runtime guarantees that the version in any cachable slot will never be + * 0. This should ensure that, if the version and cache pointer mismatch, the + * next access will cause a cache miss. + * + * When using a cached slot, you should compare the owner pointer to the isa + * pointer of the receiver and the message and the version of the slot to your + * cached version. + */ +struct objc_slot +{ + /** The class to which this slot is attached (used internally). */ + Class owner; + /** The class for which this slot was cached. Note that this can be + * modified by different cache owners, in different threads. Doing so may + * cause some cache misses, but if different methods are sending messages + * to the same object and sharing a cached slot then it may also improve + * cache hits. Profiling is probably required here. */ + Class cachedFor; + /** The (typed) selector for the method identified by this slot. */ + const char *types; + /** The current version. This changes if the method changes or if a + * subclass overrides this method, potentially invalidating this cache. */ + int version; + /** The method pointer for this method. */ + IMP method; + /** Selector for this method. */ + SEL selector; +} OBJC_NONPORTABLE; +#endif // __OBJC_SLOT_H_INCLUDED__ diff --git a/third_party/libobjc/objc/toydispatch.h b/third_party/libobjc/objc/toydispatch.h new file mode 100644 index 0000000000000000000000000000000000000000..373771477df9e18356909fd8e438ecec1d96c0b2 --- /dev/null +++ b/third_party/libobjc/objc/toydispatch.h @@ -0,0 +1,47 @@ +/** + * toydispatch implements a (tiny) subset of the libdispatch interfaces. It + * can produce FIFO work queues, but not concurrent ones (although queues are + * concurrent with respect to each other, as with libdispatch). Unlike + * libdispatch, queues all run on the same system thread. This is less + * efficient, so the real libdispatch should be used on platforms where it is + * available. + * + * Toydispatch symbol names are prefixed with toy_ so programs can be linked to + * both libdispatch and toydispatch. + */ + +/* If the real libdispatch exists, use that instead of the toy one. */ +#if !defined(__has_include) +#define __has_include(x) 0 +#endif +#if __has_include(<dispatch/dispatch.h>) && !defined(__TOY_DISPATCH__) +# include <dispatch/dispatch.h> +#else + +/** + * Function type for functions that can be added to dispatch queues. + */ +typedef void (*dispatch_function_t)(void *); + +typedef struct dispatch_queue * dispatch_queue_t; + +#define dispatch_queue_create toy_dispatch_queue_create +/** + * Create a new queue. Both parameters are ignored by toydispatch. + */ +dispatch_queue_t dispatch_queue_create(const char *label, void *attr); + +#define dispatch_async_f toy_dispatch_async_f +/** + * Add a function to the queue. + */ +void dispatch_async_f(dispatch_queue_t queue, + void *context, + dispatch_function_t work); + +#define dispatch_release toy_dispatch_release +void dispatch_release(dispatch_queue_t queue); + +#define dispatch_retain toy_dispatch_retain +void dispatch_retain(dispatch_queue_t queue); +#endif diff --git a/third_party/libobjc/objc_msgSend.S b/third_party/libobjc/objc_msgSend.S new file mode 100644 index 0000000000000000000000000000000000000000..10e52f344c8559a5a05d58259661a5428d345c5c --- /dev/null +++ b/third_party/libobjc/objc_msgSend.S @@ -0,0 +1,9 @@ +#if __x86_64 +#include "objc_msgSend.x86-64.S" +#elif __i386 +#include "objc_msgSend.x86-32.S" +#elif __arm__ +#include "objc_msgSend.arm.S" +#else +#warning objc_msgSend() not implemented for your architecture +#endif diff --git a/third_party/libobjc/objc_msgSend.arm.S b/third_party/libobjc/objc_msgSend.arm.S new file mode 100644 index 0000000000000000000000000000000000000000..4934996743070ef0d3744c04cbc25e2c99e9abb4 --- /dev/null +++ b/third_party/libobjc/objc_msgSend.arm.S @@ -0,0 +1,113 @@ +#define DTABLE_OFFSET 32 +#define SMALLOBJ_MASK 1 +#define SHIFT_OFFSET 4 +#define DATA_OFFSET 12 +#define SLOT_OFFSET 16 +.syntax unified +.fpu neon + +// Macro for testing: logs a register value to standard error +.macro LOG reg + push {r0-r3, ip,lr} + mov r0, \reg + bl logInt(PLT) + pop {r0-r3, ip,lr} +.endm + +.macro MSGSEND receiver, sel + .fnstart + teq \receiver, 0 + beq 4f // Skip everything if the receiver is nil + push {r4-r6} // We're going to use these three as + .save {r4-r6} + // scratch registers, so save them now. + // These are callee-save, so the unwind library + // must be able to restore them, so we need CFI + // directives for them, but not for any other pushes + tst \receiver, SMALLOBJ_MASK // Sets Z if this is not a small int + + + ldrne r4, LSmallIntClass // Small Int class -> r4 if this is a small int + ldrne r4, [r4] + + ldreq r4, [\receiver] // Load class to r4 if not a small int + + ldr r4, [r4, #DTABLE_OFFSET] // Dtable -> r4 + + ldr r5, LUninstalledDtable // &uninstalled_dtable -> r5 + ldr r5, [r5] + + teq r4, r5 // If dtable == &uninstalled_dtable + beq 5f // Do a slow lookup + + ldr r5, [\sel] // selector->index -> r5 + + ldr r6, [r4, #SHIFT_OFFSET] // dtable->shift -> r6 + ldr r4, [r4, #DATA_OFFSET] // dtable->data -> r4 + + teq r6, #8 // If this is a small dtable, jump to the small dtable handlers + beq 1f + teq r6, #0 + beq 2f + + and r6, r5, #0xff0000 + ldr r4, [r4, r6, asr#14] + ldr r4, [r4, #DTABLE_OFFSET] +1: // dtable16 + and r6, r5, #0xff00 + ldr r4, [r4, r6, asr#6] + ldr r4, [r4, #DTABLE_OFFSET] +2: // dtable8 + and r6, r5, #0xff + ldr ip, [r4, r6, asl#2] + + teq ip, #0 // If the slot is nil + beq 5f // Go to the slow path and do the forwarding stuff + + ldr ip, [ip, #SLOT_OFFSET] // Load the method from the slot + +3: + pop {r4-r6} // Restore the saved callee-save registers + mov pc, ip + +4: // Nil receiver + mov r0, 0 + mov r1, 0 + mov pc, lr +5: // Slow lookup + push {r0-r4, lr} // Save anything that will be clobbered by the call + .save {r0-r4, lr} + + + push {\receiver} // &self, _cmd in arguments + .save {\receiver} + mov r1, \sel + + bl slowMsgLookup(PLT) // This is the only place where the CFI directives have to be accurate... + mov ip, r0 // IMP -> ip + + pop {r5} // restore (modified) self to r5 + pop {r0-r4, lr} // Load clobbered registers + mov \receiver, r5 + b 3b + .fnend +.endm + +.globl objc_msgSend_fpret + .type objc_msgSend_fpret, %function +.globl objc_msgSend + .type objc_msgSend, %function +objc_msgSend: +objc_msgSend_fpret: + MSGSEND r0, r1 +.globl objc_msgSend_stret + .type objc_msgSend_stret, %function +objc_msgSend_stret: + MSGSEND r1, r2 + +LSmallIntClass: + .long SmallObjectClasses + .align 2 +LUninstalledDtable: + .long uninstalled_dtable + .align 2 diff --git a/third_party/libobjc/objc_msgSend.x86-32.S b/third_party/libobjc/objc_msgSend.x86-32.S new file mode 100644 index 0000000000000000000000000000000000000000..f55f8c3c44f03a7ce5e23ef8b8d11180a2ceb125 --- /dev/null +++ b/third_party/libobjc/objc_msgSend.x86-32.S @@ -0,0 +1,100 @@ +#define DTABLE_OFFSET 32 +#define SMALLOBJ_MASK 1 +#define SHIFT_OFFSET 4 +#define DATA_OFFSET 12 +#define SLOT_OFFSET 16 +.macro MSGSEND receiver, sel, fpret + .cfi_startproc + movl \receiver(%esp), %eax + test %eax, %eax # If the receiver is nil + jz 4f # return nil + test $SMALLOBJ_MASK, %eax # Check if the receiver is a small object + jnz 6f # Get the small object class + + mov (%eax), %eax # Load the class +1: # classLoaded + movl \sel(%esp), %ecx + mov DTABLE_OFFSET(%eax), %eax # Load the dtable from the class + cmpl uninstalled_dtable, %eax # If this is not (yet) a valid dtable + je 5f # Do a slow lookup + + mov (%ecx), %ecx # Load the selector index + + # Register use at this point: + # %eax: dtable + # %ecx: Selector index + # %edx: selector index fragment + + mov SHIFT_OFFSET(%eax), %edx # Load the shift (dtable size) + mov DATA_OFFSET(%eax), %eax # load the address of the start of the array + cmpl $8, %edx # If this is a small dtable, jump to the small dtable handlers + je 2f + cmpl $0, %edx + je 3f + + mov %ecx, %edx + and $0xff0000, %edx + shrl $14, %edx # Right shift 16, but then left shift by 2 (* sizeof(void*)) + add %edx, %eax + mov (%eax), %eax + mov DATA_OFFSET(%eax), %eax +2: # dtable16: + mov %ecx, %edx + and $0xff00, %edx + shrl $6, %edx + add %edx, %eax + mov (%eax), %eax + mov DATA_OFFSET(%eax), %eax +3: # dtable8: + and $0xff, %ecx + shll $2, %ecx + add %ecx, %eax + mov (%eax), %eax + + test %eax, %eax + jz 5f # Nil slot - invoke some kind of forwarding mechanism + mov SLOT_OFFSET(%eax), %eax + jmp *%eax +4: # returnNil: +.if \fpret + fldz +.else + xor %eax, %eax # return 0 (int) + xor %edx, %edx # Return 64-bit zero (%edx is + # caller-save, so it's safe to do this in the general case. +.endif + ret +5: # slowSend: + mov \sel(%esp), %ecx + lea \receiver(%esp), %eax + + push %ecx # _cmd + push %eax # &self + .cfi_def_cfa_offset 12 + call slowMsgLookup@PLT + add $8, %esp # restore the stack + + + jmp *%eax +6: # smallObject: + push %ebx # Save old %ebx + call __i686.get_pc_thunk.bx + addl $_GLOBAL_OFFSET_TABLE_, %ebx + mov SmallObjectClasses@GOT(%ebx), %eax + mov (%eax), %eax + popl %ebx + jmp 1b + .cfi_endproc +.endm +.globl objc_msgSend_fpret + .type objc_msgSend_fpret, @function +objc_msgSend_fpret: + MSGSEND 4, 8, 1 +.globl objc_msgSend + .type objc_msgSend, @function +objc_msgSend: + MSGSEND 4, 8, 0 +.globl objc_msgSend_stret + .type objc_msgSend_stret, @function +objc_msgSend_stret: + MSGSEND 8, 12, 0 diff --git a/third_party/libobjc/objc_msgSend.x86-64.S b/third_party/libobjc/objc_msgSend.x86-64.S new file mode 100644 index 0000000000000000000000000000000000000000..8ce012baf0dc47455cadde3d0808c58012334522 --- /dev/null +++ b/third_party/libobjc/objc_msgSend.x86-64.S @@ -0,0 +1,144 @@ +#define DTABLE_OFFSET 64 +#define SMALLOBJ_MASK 7 +#define SHIFT_OFFSET 4 +#define DATA_OFFSET 16 +#define SLOT_OFFSET 32 + +.macro MSGSEND receiver, sel + .cfi_startproc # Start emitting unwind data. We + # don't actually care about any of + # the stuff except the slow call, + # because that's the only one that + # can throw. + + test \receiver, \receiver # If the receiver is nil + jz 4f # return nil + movq $SMALLOBJ_MASK, %r10 # Load the small object mask + test \receiver, %r10 # Check if the receiver is a small object + jnz 6f # Get the small object class + + mov (\receiver), %r10 # Load the dtable from the class +1: # classLoaded + mov DTABLE_OFFSET(%r10), %r10 # Load the dtable from the class + cmpq uninstalled_dtable(%rip), %r10 # If this is not (yet) a valid dtable + je 5f # Do a slow lookup + + push %r12 + push %r13 + + mov (\sel), %r11 # Load the selector index + mov SHIFT_OFFSET(%r10), %r13 # Load the shift (dtable size) + mov DATA_OFFSET(%r10), %r12 # load the address of the start of the array + cmpl $8, %r13d # If this is a small dtable, jump to the small dtable handlers + je 2f + cmpl $0, %r13d + je 3f + + mov %r11, %r13 + and $0xff0000, %r13 + shrl $13, %r13d # Right shift 16, but then left shift by 3 *sizeof(void*) + add %r13, %r12 + mov (%r12), %r12 + mov DATA_OFFSET(%r12), %r12 +2: # dtable16: + mov %r11, %r13 + and $0xff00, %r13 + shrl $5, %r13d + add %r13, %r12 + mov (%r12), %r12 + mov DATA_OFFSET(%r12), %r12 +3: # dtable8: + mov %r11, %r13 + and $0xff, %r13 + shll $3, %r13d + add %r13, %r12 + mov (%r12), %r10 + pop %r13 + pop %r12 + test %r10, %r10 + jz 5f # Nil slot - invoke some kind of forwarding mechanism + mov SLOT_OFFSET(%r10), %r10 + jmp *%r10 +4: # returnNil: + # Both of the return registers are + # callee-save on x86-64, so we can + # return 0 in both in the same code: + xor %rax, %rax # Return 0 as an integer + pxor %xmm0, %xmm0 # Return 0 as a floating point value + ret +5: # slowSend: + push %rax # We need to preserve all registers that may contain arguments: + push %rbx + push %rcx + push %r8 + push %r9 + + sub $0x98, %rsp + movups %xmm0, 0x80(%rsp) + movups %xmm1, 0x70(%rsp) + movups %xmm2, 0x60(%rsp) + movups %xmm3, 0x50(%rsp) + movups %xmm4, 0x40(%rsp) + movups %xmm5, 0x30(%rsp) + movups %xmm6, 0x20(%rsp) + movups %xmm7, 0x10(%rsp) + +#rdi rsi rdx + # We're (potentially) modifying the self argument with the lookup, so we don't want to be +.ifc "\receiver", "%rdi" + push %rdi + mov %rsp, %rdi + push %rsi # Save _cmd (not preserved across calls) + push %rdx +.else + push %rdi # Save the sret pointer + push %rsi # Save self where it can be modified + mov %rsp, %rdi + push %rdx + mov %rdx, %rsi # move _cmd to where the callee expects it to be +.endif + + .cfi_adjust_cfa_offset 0xD8 + call slowMsgLookup # Call the slow lookup function + mov %rax, %r10 # Load the returned IMP + + pop %rdx + pop %rsi + pop %rdi + + movups 0x80(%rsp), %xmm0 + movups 0x70(%rsp), %xmm1 + movups 0x60(%rsp), %xmm2 + movups 0x50(%rsp), %xmm3 + movups 0x40(%rsp), %xmm4 + movups 0x30(%rsp), %xmm5 + movups 0x20(%rsp), %xmm6 + movups 0x10(%rsp), %xmm7 + add $0x98, %rsp + + pop %r9 + pop %r8 + pop %rcx + pop %rbx + pop %rax + jmp *%r10 +6: # smallObject: + and \receiver, %r10 # Find the small int type + shll $3, %r10d + lea SmallObjectClasses(%rip), %r11 + add %r11, %r10 + mov (%r10), %r10 + jmp 1b + .cfi_endproc +.endm +.globl objc_msgSend + .type objc_msgSend, @function +.globl objc_msgSend_fpret + .type objc_msgSend_fpret, @function +objc_msgSend_fpret: +objc_msgSend: + MSGSEND %rdi, %rsi +.globl objc_msgSend_stret + .type objc_msgSend_stret, @function +objc_msgSend_stret: + MSGSEND %rsi, %rdx diff --git a/third_party/libobjc/objcxx_eh.cc b/third_party/libobjc/objcxx_eh.cc new file mode 100644 index 0000000000000000000000000000000000000000..0a29c3ce7378082d5942e557eb60e95501783454 --- /dev/null +++ b/third_party/libobjc/objcxx_eh.cc @@ -0,0 +1,241 @@ +#include <stdlib.h> +#include <stdio.h> +#include "dwarf_eh.h" +#include "objcxx_eh.h" +#include <exception> + +extern "C" +{ +#include "objc/runtime.h" +}; +namespace __cxxabiv1 +{ + struct __class_type_info; +} + +using __cxxabiv1::__class_type_info; + +namespace std +{ + /** + * std::type_info defined with the GCC ABI. This may not be exposed in + * public headers, but is required for correctly implementing the unified + * exception model. + */ + class type_info + { + public: + virtual ~type_info(); + bool operator==(const type_info &) const; + bool operator!=(const type_info &) const; + bool before(const type_info &) const; + const char* name() const; + type_info(); + private: + type_info(const type_info& rhs); + type_info& operator= (const type_info& rhs); + const char *__type_name; + protected: + type_info(const char *name): __type_name(name) { } + public: + virtual bool __is_pointer_p() const; + virtual bool __is_function_p() const; + virtual bool __do_catch(const type_info *thrown_type, + void **thrown_object, + unsigned outer) const; + virtual bool __do_upcast( + const __class_type_info *target, + void **thrown_object) const; + }; +} + +using namespace std; + + +static BOOL isKindOfClass(Class thrown, Class type) +{ + do + { + if (thrown == type) + { + return YES; + } + thrown = class_getSuperclass(thrown); + } while (Nil != thrown); + + return NO; +} + +/** + * C++ Exception structure. From the Itanium ABI spec + */ +struct __cxa_exception +{ + std::type_info *exceptionType; + void (*exceptionDestructor) (void *); + unexpected_handler unexpectedHandler; + terminate_handler terminateHandler; + __cxa_exception *nextException; + unsigned int handlerCount; + int handlerSwitchValue; + const char *actionRecord; + const char *languageSpecificData; + void *catchTemp; + void *adjustedPtr; + _Unwind_Exception unwindHeader; +}; + + + + +namespace gnustep +{ + namespace libobjc + { + struct __objc_id_type_info : std::type_info + { + __objc_id_type_info() : type_info("@id") {}; + virtual ~__objc_id_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + struct __objc_class_type_info : std::type_info + { + virtual ~__objc_class_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + } +}; + + +static bool AppleCompatibleMode = true; +extern "C" int objc_set_apple_compatible_objcxx_exceptions(int newValue) +{ + bool old = AppleCompatibleMode; + AppleCompatibleMode = newValue; + return old; +} + +gnustep::libobjc::__objc_class_type_info::~__objc_class_type_info() {} +gnustep::libobjc::__objc_id_type_info::~__objc_id_type_info() {} +bool gnustep::libobjc::__objc_class_type_info::__do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const +{ + id thrown = (id)obj; + bool found = false; + // Id throw matches any ObjC catch. This may be a silly idea! + if (dynamic_cast<const __objc_id_type_info*>(thrownType) + || (AppleCompatibleMode && + dynamic_cast<const __objc_class_type_info*>(thrownType))) + { + thrown = **(id**)obj; + // nil only matches id catch handlers in Apple-compatible mode, or when thrown as an id + if (0 == thrown) + { + return false; + } + // Check whether the real thrown object matches the catch type. + found = isKindOfClass(object_getClass(thrown), + (Class)objc_getClass(name())); + } + else if (dynamic_cast<const __objc_class_type_info*>(thrownType)) + { + thrown = **(id**)obj; + found = isKindOfClass((Class)objc_getClass(thrownType->name()), + (Class)objc_getClass(name())); + } + if (found) + { + *obj = (void*)thrown; + } + return found; +}; + +bool gnustep::libobjc::__objc_id_type_info::__do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const +{ + // Id catch matches any ObjC throw + if (dynamic_cast<const __objc_class_type_info*>(thrownType)) + { + *obj = **(id**)obj; + return true; + } + if (dynamic_cast<const __objc_id_type_info*>(thrownType)) + { + *obj = **(id**)obj; + return true; + } + return false; +}; + +/** + * Public interface to the Objective-C++ exception mechanism + */ +extern "C" +{ +/** + * The public symbol that the compiler uses to indicate the Objective-C id type. + */ +gnustep::libobjc::__objc_id_type_info __objc_id_type_info; + +/** + * Exception cleanup function for C++ exceptions that wrap Objective-C + * exceptions. + */ +static void exception_cleanup(_Unwind_Reason_Code reason, + struct _Unwind_Exception *ex) +{ + __cxa_exception *cxxex = (__cxa_exception*) ((char*)ex - offsetof(struct __cxa_exception, unwindHeader)); + if (cxxex->exceptionType != &__objc_id_type_info) + { + delete cxxex->exceptionType; + } + __cxa_free_exception((void*)ex); +} + +struct _Unwind_Exception *objc_init_cxx_exception(void *thrown_exception) +{ + __cxa_exception *ex = ((__cxa_exception*)thrown_exception) - 1; + + std::type_info *tinfo = &__objc_id_type_info; + + ex->exceptionType = tinfo; + + ex->exceptionDestructor = 0; + + ex->unwindHeader.exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); + ex->unwindHeader.exception_cleanup = exception_cleanup; + + return &ex->unwindHeader; +} + +void* objc_object_for_cxx_exception(void *thrown_exception) +{ + __cxa_exception *ex = (__cxa_exception*) ((char*)thrown_exception - + offsetof(struct __cxa_exception, unwindHeader)); + const std::type_info *thrownType = ex->exceptionType; + if (!dynamic_cast<const gnustep::libobjc::__objc_id_type_info*>(thrownType) && + !dynamic_cast<const gnustep::libobjc::__objc_class_type_info*>(thrownType)) + { + return (id)-1; + } + return *(id*)(ex+1); +} + +/* +void print_type_info(void *thrown_exception) +{ + __cxa_exception *ex = (__cxa_exception*) ((char*)thrown_exception - + offsetof(struct __cxa_exception, unwindHeader)); + fprintf(stderr, "Type info: %s\n", ex->exceptionType->name()); + fprintf(stderr, "offset is: %d\n", offsetof(struct __cxa_exception, unwindHeader)); +} +*/ + +} // extern "C" + diff --git a/third_party/libobjc/objcxx_eh.h b/third_party/libobjc/objcxx_eh.h new file mode 100644 index 0000000000000000000000000000000000000000..9866105504cb77f57d009a9eb06abb853ee827ea --- /dev/null +++ b/third_party/libobjc/objcxx_eh.h @@ -0,0 +1,47 @@ +#ifdef __cplusplus +extern "C" { +#endif +/** + * Allocates a C++ exception. This function is part of the Itanium C++ ABI and + * is provided externally. + */ +__attribute__((weak)) +void *__cxa_allocate_exception(size_t thrown_size); +/** + * Initialises an exception object returned by __cxa_allocate_exception() for + * storing an Objective-C object. The return value is the location of the + * _Unwind_Exception structure within this structure, and should be passed to + * the C++ personality function. + */ +__attribute__((weak)) +struct _Unwind_Exception *objc_init_cxx_exception(void *thrown_exception); +/** + * The GNU C++ exception personality function, provided by libsupc++ (GNU) or + * libcxxrt (PathScale). + */ +__attribute__((weak)) DECLARE_PERSONALITY_FUNCTION(__gxx_personality_v0); +/** + * Frees an exception object allocated by __cxa_allocate_exception(). Part of + * the Itanium C++ ABI. + */ +__attribute__((weak)) +void __cxa_free_exception(void *thrown_exception); +/** + * Tests whether a C++ exception contains an Objective-C object, and returns if + * if it does. Returns -1 if it doesn't. -1 is used instead of 0, because + * throwing nil is allowed, but throwing non-nil, invalid objects is not. + */ +__attribute__((weak)) +void *objc_object_for_cxx_exception(void *thrown_exception); + +/** + * Prints the type info associated with an exception. Used only when + * debugging, not compiled in the normal build. + */ +__attribute__((weak)) +void print_type_info(void *thrown_exception); + + +#ifdef __cplusplus +} +#endif diff --git a/third_party/libobjc/opts/CMakeLists.txt b/third_party/libobjc/opts/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e017de8fb0b45e9c0f2d12da0564541a7e1ed401 --- /dev/null +++ b/third_party/libobjc/opts/CMakeLists.txt @@ -0,0 +1,22 @@ +add_llvm_loadable_module( libGNUObjCRuntime + ClassIMPCache.cpp + ClassMethodInliner.cpp + IvarPass.cpp + ObjectiveCOpts.cpp + TypeFeedbackDrivenInliner.cpp + ClassLookupCache.cpp + IMPCacher.cpp + LoopIMPCachePass.cpp + TypeFeedback.cpp +) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-variadic-macros -DLLVM_MAJOR=3 -DLLVM_MINOR=0") + +add_llvm_library_dependencies( libGNUObjCRuntime + LLVMAnalysis + LLVMCore + LLVMSupport + LLVMTarget + LLVMipa + LLVMipo +) diff --git a/third_party/libobjc/opts/COPYING b/third_party/libobjc/opts/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..8647a56b739dfc73c8501a1a06ecbd7e75f737e4 --- /dev/null +++ b/third_party/libobjc/opts/COPYING @@ -0,0 +1,20 @@ +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. + diff --git a/third_party/libobjc/opts/ClassIMPCache.cpp b/third_party/libobjc/opts/ClassIMPCache.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e427eeca6cec0c8e52c634f3c553142153a722dc --- /dev/null +++ b/third_party/libobjc/opts/ClassIMPCache.cpp @@ -0,0 +1,112 @@ +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/LLVMContext.h" +#include "llvm/Instructions.h" +#include "llvm/Constants.h" +#include "llvm/GlobalVariable.h" +#include "llvm/Support/CallSite.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/DefaultPasses.h" +#include "ObjectiveCOpts.h" +#include "IMPCacher.h" +#include <string> +#include "LLVMCompat.h" + +using namespace GNUstep; +using namespace llvm; +using std::string; + +namespace +{ + class ClassIMPCachePass : public ModulePass + { + LLVMIntegerType *IntTy; + + public: + static char ID; + ClassIMPCachePass() : ModulePass(ID) {} + + virtual bool runOnModule(Module &M) { + Function *sendFn = M.getFunction("objc_msgSend"); + Function *send_stretFn = M.getFunction("objc_msgSend_stret"); + Function *send_fpretFn = M.getFunction("objc_msgSend_fpret"); + Function *lookupFn =M.getFunction("objc_msg_lookup_sender"); + // If this module doesn't contain any message sends, then skip it + if ((sendFn == 0) && (send_stretFn == 0) && (send_fpretFn == 0) && + (lookupFn ==0)) { return false; } + + GNUstep::IMPCacher cacher = GNUstep::IMPCacher(M.getContext(), this); + IntTy = (sizeof(int) == 4 ) ? Type::getInt32Ty(M.getContext()) : + Type::getInt64Ty(M.getContext()) ; + bool modified = false; + + unsigned MessageSendMDKind = M.getContext().getMDKindID("GNUObjCMessageSend"); + + for (Module::iterator F=M.begin(), fend=M.end() ; + F != fend ; ++F) { + + if (F->isDeclaration()) { continue; } + + SmallVector<std::pair<CallSite, bool>, 16> Lookups; + SmallVector<CallSite, 16> Sends; + + for (Function::iterator i=F->begin(), end=F->end() ; + i != end ; ++i) { + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + CallSite call(b); + if (call.getInstruction()) { + Value *callee = call.getCalledValue()->stripPointerCasts(); + if (Function *func = dyn_cast<Function>(callee)) { + if ((func == lookupFn) || (func == sendFn) || + (func == send_fpretFn) || (func == send_stretFn)) { + MDNode *messageType = + call.getInstruction()->getMetadata(MessageSendMDKind); + if (0 == messageType) { continue; } + if (cast<ConstantInt>(messageType->getOperand(2))->isOne()) { + if (func == lookupFn) { + Lookups.push_back(std::pair<CallSite, bool>(call, false)); + } else { + Sends.push_back(call); + } + } + } else if (func->getName() == "objc_slot_lookup_super") { + Lookups.push_back(std::pair<CallSite, bool>(call, true)); + } + } + } + } + } + for (SmallVectorImpl<CallSite>::iterator i=Sends.begin(), + e=Sends.end() ; e!=i ; i++) { + Lookups.push_back(std::pair<CallSite, bool>(cacher.SplitSend(*i), false)); + } + for (SmallVectorImpl<std::pair<CallSite, bool> >::iterator + i=Lookups.begin(), e=Lookups.end() ; e!=i ; i++) { + Instruction *call = i->first.getInstruction(); + LLVMType *SlotPtrTy = call->getType(); + + Value *slot = new GlobalVariable(M, SlotPtrTy, false, + GlobalValue::PrivateLinkage, Constant::getNullValue(SlotPtrTy), + "slot"); + Value *version = new GlobalVariable(M, IntTy, false, + GlobalValue::PrivateLinkage, Constant::getNullValue(IntTy), + "version"); + cacher.CacheLookup(call, slot, version, i->second); + } + } + return modified; + } + }; + + char ClassIMPCachePass::ID = 0; + RegisterPass<ClassIMPCachePass> X("gnu-class-imp-cache", + "Cache IMPs for class messages"); +} + +ModulePass *createClassIMPCachePass(void) +{ + return new ClassIMPCachePass(); +} diff --git a/third_party/libobjc/opts/ClassLookupCache.cpp b/third_party/libobjc/opts/ClassLookupCache.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f74cf0e4678f779cffb320482aca05b7b2436db --- /dev/null +++ b/third_party/libobjc/opts/ClassLookupCache.cpp @@ -0,0 +1,160 @@ +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/Instructions.h" +#include "llvm/Constants.h" +#include "llvm/GlobalVariable.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/DefaultPasses.h" +#include "ObjectiveCOpts.h" +#include <string> + +#include "IMPCacher.h" +#include "LLVMCompat.h" + +using namespace llvm; +using namespace GNUstep; +using std::string; +using std::pair; + +namespace +{ + class ClassLookupCachePass : public ModulePass { + /// Module that we're currently optimising + Module *M; + /// Static cache. If we're not using the non-fragile ABI, then we cache + /// all class lookups in static variables to avoid the overhead of the + /// lookup. With the non-fragile ABI, we don't need to do this. + llvm::StringMap<GlobalVariable*> statics; + + typedef std::pair<CallInst*,std::string> ClassLookup; + + public: + static char ID; + ClassLookupCachePass() : ModulePass(ID) {} + + virtual bool doInitialization(Module &Mod) { + M = &Mod; + return false; + } + + bool runOnFunction(Function &F) { + bool modified = false; + SmallVector<ClassLookup, 16> Lookups; + + for (Function::iterator i=F.begin(), end=F.end() ; + i != end ; ++i) { + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + if (CallInst *call = dyn_cast<CallInst>(b)) { + if (Function *func = call->getCalledFunction()) { + if (func->getName() == "objc_lookup_class") { + ClassLookup lookup; + GlobalVariable *classNameVar = dyn_cast<GlobalVariable>( + call->getOperand(0)->stripPointerCasts()); + if (0 == classNameVar) { continue; } +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + ConstantDataArray *init = dyn_cast<ConstantDataArray>( + classNameVar->getInitializer()); +#else + ConstantArray *init = dyn_cast<ConstantArray>( + classNameVar->getInitializer()); +#endif + if (0 == init || !init->isCString()) { continue; } + lookup.first = call; + lookup.second = init->getAsString(); + modified = true; + Lookups.push_back(lookup); + } + } + } + } + } + for (SmallVectorImpl<ClassLookup>::iterator i=Lookups.begin(), + e=Lookups.end() ; e!=i ; i++) { + llvm::Instruction *lookup = i->first; + std::string &cls = i->second; + LLVMType *clsTy = lookup->getType(); + Value *global = M->getGlobalVariable(("_OBJC_CLASS_" + i->second).c_str(), true); + global = 0; + // If we can see the class reference for this, then reference it + // directly. If not, then do the lookup and cache it. + if (global) { + // Insert a bitcast of the class to the required type where the + // lookup is and then replace all references to the lookup with it. + Value *cls = new BitCastInst(global, clsTy, "class", lookup); + lookup->replaceAllUsesWith(cls); + lookup->removeFromParent(); + } else { + GlobalVariable *cache = statics[cls]; + if (!cache) { + cache = new GlobalVariable(*M, clsTy, false, + GlobalVariable::PrivateLinkage, Constant::getNullValue(clsTy), + ".class_cache"); + statics[cls] = cache; + } + BasicBlock *beforeLookupBB = lookup->getParent(); + BasicBlock *lookupBB = SplitBlock(beforeLookupBB, lookup, this); + BasicBlock::iterator iter = lookup; + iter++; + BasicBlock *afterLookupBB = SplitBlock(iter->getParent(), iter, this); + // SplitBlock() adds an unconditional branch, which we don't want. + // Remove it. + removeTerminator(beforeLookupBB); + removeTerminator(lookupBB); + + PHINode *phi = CreatePHI(clsTy, 2, cls, afterLookupBB->begin()); + // We replace all of the existing uses with the PHI node now, because + // we're going to add some more uses later that we don't want + // replaced. + lookup->replaceAllUsesWith(phi); + + // In the original basic block, we test whether the cache is NULL, + // and skip the lookup if it isn't. + IRBuilder<> B(beforeLookupBB); + llvm::Value *cachedClass = + B.CreateBitCast(B.CreateLoad(cache), clsTy); + llvm::Value *needsLookup = B.CreateIsNull(cachedClass); + B.CreateCondBr(needsLookup, lookupBB, afterLookupBB); + // In the lookup basic block, we just do the lookup, store it in the + // cache, and then jump to the continue block + B.SetInsertPoint(lookupBB); + B.CreateStore(lookup, cache); + B.CreateBr(afterLookupBB); + // Now we just need to set the PHI node to use the cache or the + // lookup result + phi->addIncoming(cachedClass, beforeLookupBB); + phi->addIncoming(lookup, lookupBB); + } + } + return modified; + } + virtual bool runOnModule(Module &Mod) { + statics.empty(); + M = &Mod; + bool modified = false; + + for (Module::iterator F=Mod.begin(), fend=Mod.end() ; + F != fend ; ++F) { + + if (F->isDeclaration()) { continue; } + + modified |= runOnFunction(*F); + } + + return modified; + }; + }; + + char ClassLookupCachePass::ID = 0; + RegisterPass<ClassLookupCachePass> X("gnu-class-lookup-cache", + "Cache class lookups"); +} + +ModulePass *createClassLookupCachePass(void) +{ + return new ClassLookupCachePass(); +} diff --git a/third_party/libobjc/opts/ClassMethodInliner.cpp b/third_party/libobjc/opts/ClassMethodInliner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dff8b7154a5cdb481f059ce4c26da0b178893a78 --- /dev/null +++ b/third_party/libobjc/opts/ClassMethodInliner.cpp @@ -0,0 +1,114 @@ +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/Instructions.h" +#include "llvm/Support/CallSite.h" +#include "llvm/Constants.h" +#include "llvm/LLVMContext.h" +#include "llvm/GlobalVariable.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/Analysis/InlineCost.h" +#include "llvm/DefaultPasses.h" +#include "ObjectiveCOpts.h" +#include "IMPCacher.h" +#include <string> + +using namespace llvm; +using namespace GNUstep; +using std::string; + +// Mangle a method name +// +// From clang: +static std::string SymbolNameForMethod(const std::string &ClassName, const + std::string &CategoryName, const std::string &MethodName, bool isClassMethod) +{ + std::string MethodNameColonStripped = MethodName; + std::replace(MethodNameColonStripped.begin(), MethodNameColonStripped.end(), + ':', '_'); + return std::string(isClassMethod ? "_c_" : "_i_") + ClassName + "_" + + CategoryName + "_" + MethodNameColonStripped; +} + +namespace +{ + class ClassMethodInliner : public ModulePass + { + const IntegerType *IntTy; + + public: + static char ID; + ClassMethodInliner() : ModulePass(ID) {} + + virtual bool runOnModule(Module &M) { + unsigned MessageSendMDKind = M.getContext().getMDKindID("GNUObjCMessageSend"); + InlineCostAnalyzer CA; + SmallPtrSet<const Function *, 16> NeverInline; + + GNUstep::IMPCacher cacher = GNUstep::IMPCacher(M.getContext(), this); + IntTy = (sizeof(int) == 4 ) ? Type::getInt32Ty(M.getContext()) : + Type::getInt64Ty(M.getContext()) ; + bool modified = false; + + for (Module::iterator F=M.begin(), fend=M.end() ; + F != fend ; ++F) { + + SmallVector<CallSite, 16> messages; + + if (F->isDeclaration()) { continue; } + + for (Function::iterator i=F->begin(), end=F->end() ; + i != end ; ++i) { + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + CallSite call(b); + if (call.getInstruction() && !call.getCalledFunction()) { + MDNode *messageType = call->getMetadata(MessageSendMDKind); + if (0 == messageType) { continue; } + messages.push_back(call); + } + } + } + for (SmallVectorImpl<CallSite>::iterator i=messages.begin(), + e=messages.end() ; e!=i ; i++) { + + MDNode *messageType = (*i)->getMetadata(MessageSendMDKind); + StringRef sel = + cast<MDString>(messageType->getOperand(0))->getString(); + StringRef cls = + cast<MDString>(messageType->getOperand(1))->getString(); + bool isClassMethod = + cast<ConstantInt>(messageType->getOperand(2))->isOne(); + std::string functionName = SymbolNameForMethod(cls, "", sel, isClassMethod); + Function *method = M.getFunction(functionName); + + if (0 == method || method->isDeclaration()) { continue; } + +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + InlineCost IC = CA.getInlineCost((*i), method, 200); +#else + InlineCost IC = CA.getInlineCost((*i), method, NeverInline); +#define getCost getValue +#endif + // FIXME: 200 is a random number. Pick a better one! + if (IC.isAlways() || (IC.isVariable() && IC.getCost() < 200)) { + cacher.SpeculativelyInline((*i).getInstruction(), method); + i->getInstruction()->setMetadata(MessageSendMDKind, 0); + modified = true; + } + } + } + return modified; + } + }; + + char ClassMethodInliner::ID = 0; + RegisterPass<ClassMethodInliner> X("gnu-class-method-inline", + "Inline class methods and message sends to super"); +} + +ModulePass *createClassMethodInliner(void) +{ + return new ClassMethodInliner(); +} diff --git a/third_party/libobjc/opts/IMPCacher.cpp b/third_party/libobjc/opts/IMPCacher.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b6b5f49e510c6c0f4b02fdc1579140ba3dd87a64 --- /dev/null +++ b/third_party/libobjc/opts/IMPCacher.cpp @@ -0,0 +1,303 @@ +#include "llvm/Analysis/Verifier.h" +#include "IMPCacher.h" +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/Instructions.h" +#include "llvm/Constants.h" +#include "llvm/LLVMContext.h" +#include "llvm/Metadata.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Support/CallSite.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/Cloning.h" + +#include "LLVMCompat.h" + +GNUstep::IMPCacher::IMPCacher(LLVMContext &C, Pass *owner) : Context(C), + Owner(owner) { + + PtrTy = Type::getInt8PtrTy(Context); + IntTy = (sizeof(int) == 4 ) ? Type::getInt32Ty(C) : Type::getInt64Ty(C); + IdTy = PointerType::getUnqual(PtrTy); + Value *AlreadyCachedFlagValue = MDString::get(C, "IMPCached"); + AlreadyCachedFlag = CreateMDNode(C, &AlreadyCachedFlagValue); + IMPCacheFlagKind = Context.getMDKindID("IMPCache"); +} + +void GNUstep::IMPCacher::CacheLookup(Instruction *lookup, Value *slot, Value + *version, bool isSuperMessage) { + + // If this IMP is already cached, don't cache it again. + if (lookup->getMetadata(IMPCacheFlagKind)) { return; } + + lookup->setMetadata(IMPCacheFlagKind, AlreadyCachedFlag); + bool isInvoke = false; + + BasicBlock *beforeLookupBB = lookup->getParent(); + BasicBlock *lookupBB = SplitBlock(beforeLookupBB, lookup, Owner); + BasicBlock *lookupFinishedBB = lookupBB; + BasicBlock *afterLookupBB; + + if (InvokeInst *inv = dyn_cast<InvokeInst>(lookup)) { + afterLookupBB = inv->getNormalDest(); + lookupFinishedBB = + BasicBlock::Create(Context, "done_lookup", lookupBB->getParent()); + CGBuilder B(lookupFinishedBB); + B.CreateBr(afterLookupBB); + inv->setNormalDest(lookupFinishedBB); + isInvoke = true; + } else { + BasicBlock::iterator iter = lookup; + iter++; + afterLookupBB = SplitBlock(iter->getParent(), iter, Owner); + } + + removeTerminator(beforeLookupBB); + + CGBuilder B = CGBuilder(beforeLookupBB); + // Load the slot and check that neither it nor the version is 0. + Value *versionValue = B.CreateLoad(version); + Value *receiverPtr = lookup->getOperand(0); + Value *receiver = receiverPtr; + if (!isSuperMessage) { + receiver = B.CreateLoad(receiverPtr); + } + // For small objects, we skip the cache entirely. + // FIXME: Class messages are never to small objects... + bool is64Bit = llvm::Module::Pointer64 == + B.GetInsertBlock()->getParent()->getParent()->getPointerSize(); + LLVMType *intPtrTy = is64Bit ? Type::getInt64Ty(Context) : + Type::getInt32Ty(Context); + + // Receiver as an integer + Value *receiverSmallObject = B.CreatePtrToInt(receiver, intPtrTy); + // Receiver is a small object... + receiverSmallObject = + B.CreateAnd(receiverSmallObject, is64Bit ? 7 : 1); + // Receiver is not a small object. + receiverSmallObject = + B.CreateICmpNE(receiverSmallObject, Constant::getNullValue(intPtrTy)); + // Ideally, we'd call objc_msgSend() here, but for now just skip the cache + // lookup + + Value *isCacheEmpty = + B.CreateICmpEQ(versionValue, Constant::getNullValue(IntTy)); + Value *receiverNil = + B.CreateICmpEQ(receiver, Constant::getNullValue(receiver->getType())); + + isCacheEmpty = B.CreateOr(isCacheEmpty, receiverNil); + isCacheEmpty = B.CreateOr(isCacheEmpty, receiverSmallObject); + + BasicBlock *cacheLookupBB = BasicBlock::Create(Context, "cache_check", + lookupBB->getParent()); + + B.CreateCondBr(isCacheEmpty, lookupBB, cacheLookupBB); + + // Check the cache node is current + B.SetInsertPoint(cacheLookupBB); + Value *slotValue = B.CreateLoad(slot, "slot_value"); + Value *slotVersion = B.CreateStructGEP(slotValue, 3); + // Note: Volatile load because the slot version might have changed in + // another thread. + slotVersion = B.CreateLoad(slotVersion, true, "slot_version"); + Value *slotCachedFor = B.CreateStructGEP(slotValue, 1); + slotCachedFor = B.CreateLoad(slotCachedFor, true, "slot_owner"); + Value *cls = B.CreateLoad(B.CreateBitCast(receiver, IdTy)); + Value *isVersionCorrect = B.CreateICmpEQ(slotVersion, versionValue); + Value *isOwnerCorrect = B.CreateICmpEQ(slotCachedFor, cls); + Value *isSlotValid = B.CreateAnd(isVersionCorrect, isOwnerCorrect); + // If this slot is still valid, skip the lookup. + B.CreateCondBr(isSlotValid, afterLookupBB, lookupBB); + + // Perform the real lookup and cache the result + removeTerminator(lookupFinishedBB); + // Replace the looked up slot with the loaded one + B.SetInsertPoint(afterLookupBB, afterLookupBB->begin()); + PHINode *newLookup = IRBuilderCreatePHI(&B, lookup->getType(), 3, "new_lookup"); + // Not volatile, so a redundant load elimination pass can do some phi + // magic with this later. + lookup->replaceAllUsesWith(newLookup); + + B.SetInsertPoint(lookupFinishedBB); + Value * newReceiver = receiver; + if (!isSuperMessage) { + newReceiver = B.CreateLoad(receiverPtr); + } + BasicBlock *storeCacheBB = BasicBlock::Create(Context, "cache_store", + lookupBB->getParent()); + + // Don't store the cached lookup if we are doing forwarding tricks. + // Also skip caching small object messages for now + Value *skipCacheWrite = + B.CreateOr(B.CreateICmpNE(receiver, newReceiver), receiverSmallObject); + skipCacheWrite = B.CreateOr(skipCacheWrite, receiverNil); + B.CreateCondBr(skipCacheWrite, afterLookupBB, storeCacheBB); + B.SetInsertPoint(storeCacheBB); + + // Store it even if the version is 0, because we always check that the + // version is not 0 at the start and an occasional redundant store is + // probably better than a branch every time. + B.CreateStore(lookup, slot); + B.CreateStore(B.CreateLoad(B.CreateStructGEP(lookup, 3)), version); + cls = B.CreateLoad(B.CreateBitCast(receiver, IdTy)); + B.CreateStore(cls, B.CreateStructGEP(lookup, 1)); + B.CreateBr(afterLookupBB); + + newLookup->addIncoming(lookup, lookupFinishedBB); + newLookup->addIncoming(slotValue, cacheLookupBB); + newLookup->addIncoming(lookup, storeCacheBB); +} + + +void GNUstep::IMPCacher::SpeculativelyInline(Instruction *call, Function + *function) { + BasicBlock *beforeCallBB = call->getParent(); + BasicBlock *callBB = SplitBlock(beforeCallBB, call, Owner); + BasicBlock *inlineBB = BasicBlock::Create(Context, "inline", + callBB->getParent()); + + + BasicBlock::iterator iter = call; + iter++; + + BasicBlock *afterCallBB = SplitBlock(iter->getParent(), iter, Owner); + + removeTerminator(beforeCallBB); + + // Put a branch before the call, testing whether the callee really is the + // function + IRBuilder<> B = IRBuilder<>(beforeCallBB); + Value *callee = isa<CallInst>(call) ? cast<CallInst>(call)->getCalledValue() + : cast<InvokeInst>(call)->getCalledValue(); + + const FunctionType *FTy = function->getFunctionType(); + const FunctionType *calleeTy = cast<FunctionType>( + cast<PointerType>(callee->getType())->getElementType()); + if (calleeTy != FTy) { + callee = B.CreateBitCast(callee, function->getType()); + } + + Value *isInlineValid = B.CreateICmpEQ(callee, function); + B.CreateCondBr(isInlineValid, inlineBB, callBB); + + // In the inline BB, add a copy of the call, but this time calling the real + // version. + Instruction *inlineCall = call->clone(); + Value *inlineResult= inlineCall; + inlineBB->getInstList().push_back(inlineCall); + + B.SetInsertPoint(inlineBB); + + if (calleeTy != FTy) { + for (unsigned i=0 ; i<FTy->getNumParams() ; i++) { + LLVMType *callType = calleeTy->getParamType(i); + LLVMType *argType = FTy->getParamType(i); + if (callType != argType) { + inlineCall->setOperand(i, new + BitCastInst(inlineCall->getOperand(i), argType, "", inlineCall)); + } + } + if (FTy->getReturnType() != calleeTy->getReturnType()) { + if (FTy->getReturnType() == Type::getVoidTy(Context)) { + inlineResult = Constant::getNullValue(calleeTy->getReturnType()); + } else { + inlineResult = + new BitCastInst(inlineCall, calleeTy->getReturnType(), "", inlineBB); + } + } + } + + B.CreateBr(afterCallBB); + + // Unify the return values + if (call->getType() != Type::getVoidTy(Context)) { + PHINode *phi = CreatePHI(call->getType(), 2, "", afterCallBB->begin()); + call->replaceAllUsesWith(phi); + phi->addIncoming(call, callBB); + phi->addIncoming(inlineResult, inlineBB); + } + + // Really do the real inlining + InlineFunctionInfo IFI(0, 0); + if (CallInst *c = dyn_cast<CallInst>(inlineCall)) { + c->setCalledFunction(function); + InlineFunction(c, IFI); + } else if (InvokeInst *c = dyn_cast<InvokeInst>(inlineCall)) { + c->setCalledFunction(function); + InlineFunction(c, IFI); + } +} + +CallSite GNUstep::IMPCacher::SplitSend(CallSite msgSend) +{ + BasicBlock *lookupBB = msgSend->getParent(); + Function *F = lookupBB->getParent(); + Module *M = F->getParent(); + Function *send = M->getFunction("objc_msgSend"); + Function *send_stret = M->getFunction("objc_msgSend_stret"); + Function *send_fpret = M->getFunction("objc_msgSend_fpret"); + Value *self; + Value *cmd; + int selfIndex = 0; + if ((msgSend.getCalledFunction() == send) || + (msgSend.getCalledFunction() == send_fpret)) { + self = msgSend.getArgument(0); + cmd = msgSend.getArgument(1); + } else if (msgSend.getCalledFunction() == send_stret) { + selfIndex = 1; + self = msgSend.getArgument(1); + cmd = msgSend.getArgument(2); + } else { + abort(); + return CallSite(); + } + CGBuilder B(&F->getEntryBlock(), F->getEntryBlock().begin()); + Value *selfPtr = B.CreateAlloca(self->getType()); + B.SetInsertPoint(msgSend.getInstruction()); + B.CreateStore(self, selfPtr, true); + LLVMType *impTy = msgSend.getCalledValue()->getType(); + LLVMType *slotTy = PointerType::getUnqual(StructType::get(PtrTy, PtrTy, PtrTy, + IntTy, impTy, PtrTy, NULL)); + Value *slot; + Constant *lookupFn = M->getOrInsertFunction("objc_msg_lookup_sender", + slotTy, selfPtr->getType(), cmd->getType(), PtrTy, NULL); + if (msgSend.isCall()) { + slot = B.CreateCall3(lookupFn, selfPtr, cmd, Constant::getNullValue(PtrTy)); + } else { + InvokeInst *inv = cast<InvokeInst>(msgSend.getInstruction()); + BasicBlock *callBB = SplitBlock(lookupBB, msgSend.getInstruction(), Owner); + removeTerminator(lookupBB); + B.SetInsertPoint(lookupBB); + slot = B.CreateInvoke3(lookupFn, callBB, inv->getUnwindDest(), selfPtr, cmd, + Constant::getNullValue(PtrTy)); + addPredecssor(inv->getUnwindDest(), msgSend->getParent(), lookupBB); + B.SetInsertPoint(msgSend.getInstruction()); + } + Value *imp = B.CreateLoad(B.CreateStructGEP(slot, 4)); + msgSend.setArgument(selfIndex, B.CreateLoad(selfPtr, true)); + msgSend.setCalledFunction(imp); + return CallSite(slot); +} + +// Cleanly removes a terminator instruction. +void GNUstep::removeTerminator(BasicBlock *BB) { + TerminatorInst *BBTerm = BB->getTerminator(); + + // Remove the BB as a predecessor from all of successors + for (unsigned i = 0, e = BBTerm->getNumSuccessors(); i != e; ++i) { + BBTerm->getSuccessor(i)->removePredecessor(BB); + } + + BBTerm->replaceAllUsesWith(UndefValue::get(BBTerm->getType())); + // Remove the terminator instruction itself. + BBTerm->eraseFromParent(); +} +void GNUstep::addPredecssor(BasicBlock *block, BasicBlock *oldPredecessor, + BasicBlock *newPredecessor) { + for (BasicBlock::iterator i=block->begin() ; PHINode *phi=dyn_cast<PHINode>(i) + ; ++i) { + Value *v = phi->getIncomingValueForBlock(oldPredecessor); + phi->addIncoming(v, newPredecessor); + } +} diff --git a/third_party/libobjc/opts/IMPCacher.h b/third_party/libobjc/opts/IMPCacher.h new file mode 100644 index 0000000000000000000000000000000000000000..28fd50086c8e601bd03d60cb23473c7309d021b9 --- /dev/null +++ b/third_party/libobjc/opts/IMPCacher.h @@ -0,0 +1,49 @@ +#include "LLVMCompat.h" +#include "llvm/Support/CallSite.h" +namespace llvm +{ + class BasicBlock; + class CallInst; + class Function; + class Instruction; + class IntegerType; + class LLVMContext; + class MDNode; + class Pass; + class PointerType; + class Value; +} + +using namespace llvm; + +namespace GNUstep +{ + class IMPCacher + { + private: + LLVMContext &Context; + MDNode *AlreadyCachedFlag; + unsigned IMPCacheFlagKind; + Pass *Owner; + LLVMPointerType *PtrTy; + LLVMPointerType *IdTy; + LLVMIntegerType *IntTy; + public: + IMPCacher(LLVMContext &C, Pass *owner); + void CacheLookup(Instruction *lookup, Value *slot, Value *version, bool + isSuperMessage=false); + void SpeculativelyInline(Instruction *call, Function *function); + /** + * Turns a call to objc_msgSend*() into a call to + * objc_msg_lookup_sender() and a call to the resulting IMP. The call to + * the IMP is returned. The single call is faster, but prevents caching. + * The split call allows caching, which is faster in the best case and + * slower in the worst... + */ + CallSite SplitSend(CallSite msgSend); + }; + + void removeTerminator(BasicBlock *BB); + void addPredecssor(BasicBlock *block, BasicBlock *oldPredecessor, BasicBlock + *newPredecessor); +} diff --git a/third_party/libobjc/opts/IvarPass.cpp b/third_party/libobjc/opts/IvarPass.cpp new file mode 100644 index 0000000000000000000000000000000000000000..648901bb38c7c448d01700d6b03f4c0a2e41682c --- /dev/null +++ b/third_party/libobjc/opts/IvarPass.cpp @@ -0,0 +1,173 @@ +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/Instructions.h" +#include "llvm/GlobalAlias.h" +#include "llvm/GlobalVariable.h" +#include "llvm/Constants.h" +#include "llvm/Analysis/Verifier.h" +#include "llvm/DefaultPasses.h" +#include "llvm/ADT/DenseSet.h" +#include "ObjectiveCOpts.h" +#include <string> + +using namespace llvm; +using std::string; + +typedef std::pair<Instruction*, Value*> Replacement; + +namespace llvm { +template<> struct DenseMapInfo<Replacement> { + static inline Replacement getEmptyKey() { return Replacement(0,0); } + static inline Replacement getTombstoneKey() { return Replacement(0, (Value*)-1); } + static unsigned getHashValue(const Replacement& Val) { return ((uintptr_t)Val.first) * 37U; } + static bool isEqual(const Replacement& LHS, const Replacement& RHS) { + return LHS.first == RHS.first; + } +}; +} +namespace { + class GNUNonfragileIvarPass : public FunctionPass { + + public: + static char ID; + GNUNonfragileIvarPass() : FunctionPass(ID) {} + + Module *M; + size_t PointerSize; + virtual bool doInitialization(Module &Mod) { + M = &Mod; + PointerSize = 8; + if (M->getPointerSize() == Module::Pointer32) + PointerSize = 4; + return false; + } + + std::string getSuperName(Constant *ClsStruct) { + User *super = cast<User>(ClsStruct->getOperand(1)); + if (isa<ConstantPointerNull>(super)) return ""; + GlobalVariable *name = cast<GlobalVariable>(super->getOperand(0)); +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + return cast<ConstantDataArray>(name->getInitializer())->getAsString(); +#else + return cast<ConstantArray>(name->getInitializer())->getAsString(); +#endif + } + + size_t sizeOfClass(const std::string &className) { + // This is a root class + if ("" == className) { return 0; } + // These root classes are assumed to only have one ivar: isa + if (className.compare(0, 8, "NSObject") == 0 || + className.compare(0, 6, "Object") == 0) { + return PointerSize; + } + GlobalVariable *Cls = M->getGlobalVariable("_OBJC_CLASS_" + className); + if (!Cls) return 0; + Constant *ClsStruct = Cls->getInitializer(); + // Size is initialized to be negative for the non-fragile ABI. + ConstantInt *Size = cast<ConstantInt>(ClsStruct->getOperand(5)); + int s = Size->getSExtValue(); + // If we find a fragile class in the hierarchy, don't perform the + // simplification. This means that we're the mixed ABI, so we need the + // extra indirection. + if (s > 0) return 0; + return sizeOfClass(getSuperName(ClsStruct)) - Size->getSExtValue(); + } + + size_t hardCodedOffset(const StringRef &className, + const StringRef &ivarName) { + GlobalVariable *Cls = M->getGlobalVariable(("_OBJC_CLASS_" + className).str(), true); + if (!Cls) return 0; + Constant *ClsStruct = Cls->getInitializer(); + size_t superSize = sizeOfClass(getSuperName(ClsStruct)); + if (!superSize) return 0; + ConstantStruct *IvarStruct = cast<ConstantStruct>( + cast<GlobalVariable>(ClsStruct->getOperand(6))->getInitializer()); + int ivarCount = cast<ConstantInt>(IvarStruct->getOperand(0))->getSExtValue(); + Constant *ivars = IvarStruct->getOperand(1); + for (int i=0 ; i<ivarCount ; i++) { + Constant *ivar = cast<Constant>(ivars->getOperand(i)); + GlobalVariable *name = + cast<GlobalVariable>( + cast<User>(ivar->getOperand(0))->getOperand(0)); + std::string ivarNameStr = +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + cast<ConstantDataArray>(name->getInitializer())->getAsString(); +#else + cast<ConstantArray>(name->getInitializer())->getAsString(); +#endif + // Remove the NULL terminator from the metadata string + ivarNameStr.resize(ivarNameStr.size() - 1); + if (ivarNameStr == ivarName.str()) + return superSize + + cast<ConstantInt>(ivar->getOperand(2))->getSExtValue(); + } + return 0; + } + + virtual bool runOnFunction(Function &F) { + bool modified = false; + llvm::DenseSet<Replacement> replacements; + //llvm::cerr << "IvarPass: " << F.getName() << "\n"; + for (Function::iterator i=F.begin(), end=F.end() ; + i != end ; ++i) { + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + if (LoadInst *indirectload = dyn_cast<LoadInst>(b)) { + if (LoadInst *load = dyn_cast<LoadInst>(indirectload->getOperand(0))) { + if (GlobalVariable *ivar = + dyn_cast<GlobalVariable>(load->getOperand(0))) { + StringRef variableName = ivar->getName(); + + if (!variableName.startswith("__objc_ivar_offset_")) break; + + static size_t prefixLength = strlen("__objc_ivar_offset_"); + + StringRef suffix = variableName.substr(prefixLength, + variableName.size()-prefixLength); + + std::pair<StringRef,StringRef> parts = suffix.split('.'); + StringRef className = parts.first; + StringRef ivarName = parts.second; + + // If the class, and all superclasses, are visible in this module + // then we can hard-code the ivar offset + if (size_t offset = hardCodedOffset(className, ivarName)) { + replacements.insert(Replacement(indirectload, + ConstantInt::get(indirectload->getType(), offset))); + replacements.insert(Replacement(load, 0)); + modified = true; + } else { + // If the class was compiled with the new ABI, then we have a + // direct offset variable that we can use + if (Value *offset = M->getGlobalVariable( + ("__objc_ivar_offset_value_" + suffix).str())) { + replacements.insert(Replacement(load, offset)); + modified = true; + } + } + } + } + } + } + } + for (DenseSet<Replacement>::iterator i=replacements.begin(), + end=replacements.end() ; i != end ; ++i) { + if (i->second) + i->first->replaceAllUsesWith(i->second); + i->first->removeFromParent(); + } + verifyFunction(F); + return modified; + } + }; + + char GNUNonfragileIvarPass::ID = 0; + RegisterPass<GNUNonfragileIvarPass> X("gnu-nonfragile-ivar", "Ivar fragility pass"); +} + +FunctionPass *createGNUNonfragileIvarPass(void) +{ + return new GNUNonfragileIvarPass(); +} diff --git a/third_party/libobjc/opts/LLVMCompat.h b/third_party/libobjc/opts/LLVMCompat.h new file mode 100644 index 0000000000000000000000000000000000000000..85f472ceb4dc49fe3bd47a9b4bebb061f2f30389 --- /dev/null +++ b/third_party/libobjc/opts/LLVMCompat.h @@ -0,0 +1,144 @@ +/** + * Compatibility header that wraps LLVM API breakage and lets us compile with + * old and new versions of LLVM. + * + * First LLVM version supported is 2.9. + */ + +#ifndef __LANGUAGEKIT_LLVM_HACKS__ +#define __LANGUAGEKIT_LLVM_HACKS__ +#include <llvm/Instructions.h> +#include <llvm/Metadata.h> +#include <llvm/Intrinsics.h> +#include <llvm/Support/IRBuilder.h> + + +// Only preserve names in a debug build. This simplifies the +// IR in a release build, but makes it much harder to debug. +#ifndef DEBUG + typedef llvm::IRBuilder<false> CGBuilder; +#else + typedef llvm::IRBuilder<> CGBuilder; +#endif + +__attribute((unused)) static inline +llvm::PHINode* CreatePHI(llvm::Type *Ty, + unsigned NumReservedValues, + const llvm::Twine &NameStr="", + llvm::Instruction *InsertBefore=0) { +#if LLVM_MAJOR < 3 + llvm::PHINode *phi = llvm::PHINode::Create(Ty, NameStr, InsertBefore); + phi->reserveOperandSpace(NumReservedValues); + return phi; +#else + return llvm::PHINode::Create(Ty, NumReservedValues, NameStr, InsertBefore); +#endif +} + +__attribute((unused)) static inline +llvm::PHINode* IRBuilderCreatePHI(CGBuilder *Builder, + llvm::Type *Ty, + unsigned NumReservedValues, + const llvm::Twine &NameStr="") +{ +#if LLVM_MAJOR < 3 + llvm::PHINode *phi = Builder->CreatePHI(Ty, NameStr); + phi->reserveOperandSpace(NumReservedValues); + return phi; +#else + return Builder->CreatePHI(Ty, NumReservedValues, NameStr); +#endif +} + + + +__attribute((unused)) static inline +llvm::MDNode* CreateMDNode(llvm::LLVMContext &C, + llvm::Value **V, + unsigned length=1) { +#if LLVM_MAJOR < 3 + return llvm::MDNode::get(C, V, length); +#else + llvm::ArrayRef<llvm::Value*> val(V, length); + return llvm::MDNode::get(C, val); +#endif +} + +template<typename T> +static inline +llvm::InvokeInst* IRBuilderCreateInvoke(CGBuilder *Builder, + llvm::Value *callee, + llvm::BasicBlock *dest, + llvm::BasicBlock *dest2, + T values, + const llvm::Twine &NameStr="") +{ +#if LLVM_MAJOR < 3 + return Builder->CreateInvoke(callee, dest, dest2, values.begin(), values.end(), NameStr); +#else + return Builder->CreateInvoke(callee, dest, dest2, values, NameStr); +#endif +} + +template<typename T> +static inline +llvm::CallInst* IRBuilderCreateCall(CGBuilder *Builder, + llvm::Value *callee, + T values, + const llvm::Twine &NameStr="") +{ +#if LLVM_MAJOR < 3 + return Builder->CreateCall(callee, values.begin(), values.end(), NameStr); +#else + return Builder->CreateCall(callee, values, NameStr); +#endif +} + +template<typename T> +static inline +llvm::CallInst* CreateCall(llvm::Value *callee, + T values, + const llvm::Twine &NameStr, + llvm::Instruction *before) +{ +#if LLVM_MAJOR < 3 + return llvm::CallInst::Create(callee, values.begin(), values.end(), NameStr, before); +#else + return llvm::CallInst::Create(callee, values, NameStr, before); +#endif +} + + + +#if LLVM_MAJOR < 3 +#define GetStructType(context, ...) StructType::get(context, __VA_ARGS__) +#else +#define GetStructType(context, ...) StructType::get(__VA_ARGS__) +#endif + +__attribute((unused)) static inline +llvm::Constant* GetConstantStruct(llvm::LLVMContext &C, const std::vector<llvm::Constant*> + &V, bool Packed) { +#if LLVM_MAJOR < 3 + return llvm::ConstantStruct::get(C, V, Packed); +#else + return llvm::ConstantStruct::getAnon(C, V, Packed); +#endif +} + + +#if LLVM_MAJOR < 3 +typedef const llvm::Type LLVMType; +typedef const llvm::StructType LLVMStructType; +typedef const llvm::ArrayType LLVMArrayType; +typedef const llvm::PointerType LLVMPointerType; +typedef const llvm::IntegerType LLVMIntegerType; +#else +typedef llvm::Type LLVMType; +typedef llvm::StructType LLVMStructType; +typedef llvm::ArrayType LLVMArrayType; +typedef llvm::PointerType LLVMPointerType; +typedef llvm::IntegerType LLVMIntegerType; +#endif + +#endif diff --git a/third_party/libobjc/opts/LoopIMPCachePass.cpp b/third_party/libobjc/opts/LoopIMPCachePass.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21fcfe1a3a2fd853c37bf3c6d0313fd55993af9d --- /dev/null +++ b/third_party/libobjc/opts/LoopIMPCachePass.cpp @@ -0,0 +1,124 @@ +#include "llvm/LLVMContext.h" +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/Instructions.h" +#include "llvm/Constants.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/Analysis/Verifier.h" +#include "llvm/DefaultPasses.h" +#include "ObjectiveCOpts.h" +#include "IMPCacher.h" +#include <string> + +using namespace GNUstep; +using namespace llvm; +using std::string; + +namespace +{ + class GNULoopIMPCachePass : public FunctionPass + { + GNUstep::IMPCacher *cacher; + LLVMIntegerType *IntTy; + Module *M; + bool skip; + Function *sendFn; + Function *lookupFn; + Function *send_stretFn; + Function *send_fpretFn; + + public: + static char ID; + GNULoopIMPCachePass() : FunctionPass(ID) {} + ~GNULoopIMPCachePass() { delete cacher; } + + virtual bool doInitialization(Module &Mod) { + cacher = new GNUstep::IMPCacher(Mod.getContext(), this); + IntTy = (sizeof(int) == 4 ) ? Type::getInt32Ty(Mod.getContext()) : + Type::getInt64Ty(Mod.getContext()) ; + M = &Mod; + skip = false; + sendFn = M->getFunction("objc_msgSend"); + send_stretFn = M->getFunction("objc_msgSend_stret"); + send_fpretFn = M->getFunction("objc_msgSend_fpret"); + lookupFn =M->getFunction("objc_msg_lookup_sender"); + // If this module doesn't contain any message sends, then skip it + if ((sendFn == 0) && (send_stretFn == 0) && (send_fpretFn == 0) && + (lookupFn ==0)) { + skip = true; + } + return false; + } + + virtual void getAnalysisUsage(AnalysisUsage &Info) const { + Info.addRequired<LoopInfo>(); + } + + + virtual bool runOnFunction(Function &F) { + if (skip) { return false; } + LoopInfo &LI = getAnalysis<LoopInfo>(); + bool modified = false; + SmallVector<CallSite, 16> Lookups; + SmallVector<CallSite, 16> Sends; + BasicBlock *entry = &F.getEntryBlock(); + + for (Function::iterator i=F.begin(), end=F.end() ; + i != end ; ++i) { + // Ignore basic blocks that are not parts of loops. + if (LI.getLoopDepth(i) == 0) { continue; } + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + CallSite call = CallSite(b); + if (CallSite() != call) { + Value *callee = call.getCalledValue()->stripPointerCasts(); + Function *func = dyn_cast<Function>(callee); + if (func) { + if (func == lookupFn) { + modified = true; + Lookups.push_back(call); + } else if ((func == sendFn) || (func == send_fpretFn) || + (func == send_stretFn)) { + modified = true; + Sends.push_back(call); + } + } + } + } + } + for (SmallVectorImpl<CallSite>::iterator i=Sends.begin(), + e=Sends.end() ; e!=i ; i++) { + Lookups.push_back(cacher->SplitSend(*i)); + } + IRBuilder<> B = IRBuilder<>(entry); + for (SmallVectorImpl<CallSite>::iterator i=Lookups.begin(), + e=Lookups.end() ; e!=i ; i++) { + LLVMType *SlotPtrTy = (*i)->getType(); + B.SetInsertPoint(entry, entry->begin()); + Value *slot = B.CreateAlloca(SlotPtrTy, 0, "slot"); + Value *version = B.CreateAlloca(IntTy, 0, "slot_version"); + + B.CreateStore(Constant::getNullValue(SlotPtrTy), slot); + B.CreateStore(Constant::getNullValue(IntTy), version); + cacher->CacheLookup(i->getInstruction(), slot, version); + } +#ifdef DEBUG + if (modified){ + verifyFunction(F); + } +#endif + return modified; + } + }; + + char GNULoopIMPCachePass::ID = 0; + RegisterPass<GNULoopIMPCachePass> X("gnu-loop-imp-cache", + "Cache IMPs in loops pass"); +} + +FunctionPass *createGNULoopIMPCachePass(void) +{ + return new GNULoopIMPCachePass(); +} diff --git a/third_party/libobjc/opts/ObjectiveCOpts.cpp b/third_party/libobjc/opts/ObjectiveCOpts.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9cc64048a9d9ea509c61ca6f59dbe908582d352c --- /dev/null +++ b/third_party/libobjc/opts/ObjectiveCOpts.cpp @@ -0,0 +1,92 @@ +#include "llvm/Pass.h" +#include "llvm/Module.h" +#if LLVM_MAJOR >= 3 +#include "llvm/Transforms/IPO/PassManagerBuilder.h" +#include "llvm/PassManager.h" +#endif + +#include "ObjectiveCOpts.h" + +using namespace llvm; + +namespace +{ + class ObjectiveCOpts : public ModulePass { + ModulePass *ClassIMPCachePass; + ModulePass *ClassLookupCachePass; + ModulePass *ClassMethodInliner; + FunctionPass *GNUNonfragileIvarPass; + FunctionPass *GNULoopIMPCachePass; + + public: + static char ID; + ObjectiveCOpts() : ModulePass(ID) { + ClassIMPCachePass = createClassIMPCachePass(); + ClassLookupCachePass = createClassLookupCachePass(); + ClassMethodInliner = createClassMethodInliner(); + GNUNonfragileIvarPass = createGNUNonfragileIvarPass(); + GNULoopIMPCachePass = createGNULoopIMPCachePass(); + } + virtual ~ObjectiveCOpts() { + delete ClassIMPCachePass; + delete ClassMethodInliner; + delete ClassLookupCachePass; + delete GNULoopIMPCachePass; + delete GNUNonfragileIvarPass; + } + + virtual bool runOnModule(Module &Mod) { + bool modified; + modified = ClassIMPCachePass->runOnModule(Mod); + modified |= ClassLookupCachePass->runOnModule(Mod); + modified |= ClassMethodInliner->runOnModule(Mod); + + for (Module::iterator F=Mod.begin(), fend=Mod.end() ; + F != fend ; ++F) { + + if (F->isDeclaration()) { continue; } + modified |= GNUNonfragileIvarPass->runOnFunction(*F); + modified |= GNULoopIMPCachePass->runOnFunction(*F); + } + + return modified; + }; + }; + + char ObjectiveCOpts::ID = 0; + RegisterPass<ObjectiveCOpts> X("gnu-objc", + "Run all of the GNUstep Objective-C runtimm optimisations"); + + +#if LLVM_MAJOR >= 3 + + void addObjCPasses(const PassManagerBuilder &Builder, PassManagerBase &PM) { + // Always add the ivar simplification pass + PM.add(createGNUNonfragileIvarPass()); + // Only cache IMPs in loops if we're not optimising for size. + if (Builder.SizeLevel == 0) { + PM.add(createGNULoopIMPCachePass()); + } + // Do the rest of the caching if we're not aggressively optimising for size + if (Builder.SizeLevel < 2) { + PM.add(createClassIMPCachePass()); + PM.add(createClassLookupCachePass()); + } + // Definitely don't do extra inlining if we're optimising for size! + if (Builder.SizeLevel == 0) { + PM.add(createClassMethodInliner()); + } + } + /* + static struct PluginRegister { + PluginRegister() { + PassManagerBuilder::addGlobalExtension(PassManagerBuilder::EP_LoopOptimizerEnd, + addObjCPasses); + } + } Register; + */ + RegisterStandardPasses S(PassManagerBuilder::EP_LoopOptimizerEnd, + addObjCPasses); +#endif + +} diff --git a/third_party/libobjc/opts/ObjectiveCOpts.h b/third_party/libobjc/opts/ObjectiveCOpts.h new file mode 100644 index 0000000000000000000000000000000000000000..c90452d2757778a3bf7bbd3d1ab03d7fa733a5c2 --- /dev/null +++ b/third_party/libobjc/opts/ObjectiveCOpts.h @@ -0,0 +1,7 @@ +llvm::ModulePass *createClassIMPCachePass(void); +llvm::ModulePass *createClassLookupCachePass(void); +llvm::ModulePass *createClassMethodInliner(void); +llvm::FunctionPass *createGNUNonfragileIvarPass(void); +llvm::FunctionPass *createGNULoopIMPCachePass(void); +llvm::ModulePass *createTypeFeedbackPass(void); +llvm::ModulePass *createTypeFeedbackDrivenInlinerPass(void); diff --git a/third_party/libobjc/opts/README b/third_party/libobjc/opts/README new file mode 100644 index 0000000000000000000000000000000000000000..9a646ad568684ac8e026c1e9bb38d3a479b360e6 --- /dev/null +++ b/third_party/libobjc/opts/README @@ -0,0 +1,30 @@ +GNUstep Runtime Optimisations +============================= + +This directory contains LLVM optimisations specific to libobjc2. To build +them, you must copy this directory to llvm/lib/Transforms/GNURuntime (where +llvm is the root of your llvm checkout). + +Running GNU make will then create GNUObjCRuntime.so. This library can be +passed to opt to run optimisations on bitcode generated with clang or +LanguageKit. + +Non-Fragile Ivar Pass +--------------------- + +Running `opt -gnu-nonfragile-ivar` will invoke the non-fragile instance +variable lowering pass. This will turn non-fragile instance variable accesses, +which go via one or two indirection layers, into more fragile ones. If a class +and all of its superclasses are present in the module then this pass will turn +indirect instance variable accesses into hard-coded ones. + +For this pass to be most useful, it should be run as a link-time optimisation. + +Type Feedback +------------- + +Running `opt -gnu-objc-type-feedback` enables type feedback. Objective-C +message lookups will be replaced by calls to the profiling version in the +runtime library. The generated data can then be used for future optimisations +(speculative inlining, polymorphic inline caching, and so on), which have not +yet been written. diff --git a/third_party/libobjc/opts/TypeFeedback.cpp b/third_party/libobjc/opts/TypeFeedback.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8c940ef54f299d1c3387475bd9a3f1cfe84c2df6 --- /dev/null +++ b/third_party/libobjc/opts/TypeFeedback.cpp @@ -0,0 +1,152 @@ +#include "llvm/Constants.h" +#include "llvm/Pass.h" +#include "llvm/Module.h" +#include "llvm/Function.h" +#include "llvm/Instructions.h" +#include "llvm/Support/CallSite.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Linker.h" +#include <vector> + +using namespace llvm; +#include "LLVMCompat.h" + +namespace { + struct GNUObjCTypeFeedback : public ModulePass { + + typedef std::pair<CallInst*,CallInst*> callPair; + typedef std::vector<callPair > replacementVector; + static char ID; + uint32_t callsiteCount; + LLVMIntegerType *Int32Ty; + GNUObjCTypeFeedback() : ModulePass(ID), callsiteCount(0) {} + + void profileFunction(Function &F, Constant *ModuleID) { + for (Function::iterator i=F.begin(), e=F.end() ; + i != e ; ++i) { + + Module *M = F.getParent(); + replacementVector replacements; + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + + CallSite call(b); + if (call.getInstruction() && !call.getCalledFunction()) { + llvm::SmallVector<llvm::Value*, 4> args; + args.push_back(call->getOperand(1)); + args.push_back(call->getOperand(0)), + args.push_back(ModuleID); + args.push_back(ConstantInt::get(Int32Ty, callsiteCount++)); + Constant *profile = + M->getOrInsertFunction("objc_msg_profile", + Type::getVoidTy(M->getContext()), + args[0]->getType(), args[1]->getType(), + args[2]->getType(), args[3]->getType(), NULL); + CreateCall(profile, args, "", call.getInstruction()); + } + } + } + } + + public: + virtual bool runOnModule(Module &M) + { + LLVMContext &VMContext = M.getContext(); + Int32Ty = IntegerType::get(VMContext, 32); + LLVMPointerType *PtrTy = Type::getInt8PtrTy(VMContext); + Constant *moduleName = +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + ConstantDataArray::getString(VMContext, M.getModuleIdentifier(), true); +#else + ConstantArray::get(VMContext, M.getModuleIdentifier(), true); +#endif + moduleName = new GlobalVariable(M, moduleName->getType(), true, + GlobalValue::InternalLinkage, moduleName, + ".objc_profile_module_name"); + std::vector<Constant*> functions; + + llvm::Constant *Zeros[2]; + Zeros[0] = ConstantInt::get(Type::getInt32Ty(VMContext), 0); + Zeros[1] = Zeros[0]; + + moduleName = ConstantExpr::getGetElementPtr(moduleName, Zeros, 2); + functions.push_back(moduleName);; + functions.push_back(moduleName);; + + for (Module::iterator F=M.begin(), e=M.end() ; + F != e ; ++F) { + if (F->isDeclaration()) { continue; } + functions.push_back(ConstantExpr::getBitCast(F, PtrTy)); + + Constant * ConstStr = +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + llvm::ConstantDataArray::getString(VMContext, F->getName(), true); +#else + llvm::ConstantArray::get(VMContext, F->getName()); +#endif + ConstStr = new GlobalVariable(M, ConstStr->getType(), true, + GlobalValue::PrivateLinkage, ConstStr, "str"); + functions.push_back( + ConstantExpr::getGetElementPtr(ConstStr, Zeros, 2)); + + profileFunction(*F, moduleName); + } + functions.push_back(ConstantPointerNull::get(PtrTy)); + Constant *symtab = ConstantArray::get(ArrayType::get(PtrTy, + functions.size()), functions); + Value *symbolTable = new GlobalVariable(M, symtab->getType(), true, + GlobalValue::InternalLinkage, symtab, "symtab"); + + Function *init = + Function::Create(FunctionType::get(Type::getVoidTy(VMContext), false), + GlobalValue::PrivateLinkage, "load_symbol_table", &M); + BasicBlock * EntryBB = BasicBlock::Create(VMContext, "entry", init); + IRBuilder<> B = IRBuilder<>(EntryBB); + Value *syms = B.CreateStructGEP(symbolTable, 0); + B.CreateCall(M.getOrInsertFunction("objc_profile_write_symbols", + Type::getVoidTy(VMContext), syms->getType(), NULL), + syms); + B.CreateRetVoid(); + + GlobalVariable *GCL = M.getGlobalVariable("llvm.global_ctors"); + + std::vector<Constant*> ctors; + + ConstantArray *CA = cast<ConstantArray>(GCL->getInitializer()); + + for (User::op_iterator i = CA->op_begin(), e = CA->op_end(); i != e; ++i) { + ctors.push_back(cast<ConstantStruct>(*i)); + } + + // Type of one ctor + LLVMType *ctorTy = + cast<ArrayType>(GCL->getType()->getElementType())->getElementType(); + // Add the + std::vector<Constant*> CSVals; + CSVals.push_back(ConstantInt::get(Type::getInt32Ty(VMContext),65535)); + CSVals.push_back(init); + ctors.push_back(GetConstantStruct(GCL->getContext(), CSVals, false)); + // Create the array initializer. + CA = cast<ConstantArray>(ConstantArray::get(ArrayType::get(ctorTy, + ctors.size()), ctors)); + // Create the new global and replace the old one + GlobalVariable *NGV = new GlobalVariable(CA->getType(), + GCL->isConstant(), GCL->getLinkage(), CA, "", GCL->isThreadLocal()); + GCL->getParent()->getGlobalList().insert(GCL, NGV); + NGV->takeName(GCL); + GCL->replaceAllUsesWith(NGV); + GCL->eraseFromParent(); + + return true; + } + + }; + + char GNUObjCTypeFeedback::ID = 0; + RegisterPass<GNUObjCTypeFeedback> X("gnu-objc-type-feedback", + "Objective-C type feedback for the GNU runtime.", false, true); +} + +ModulePass *createTypeFeedbackPass(void) { + return new GNUObjCTypeFeedback(); +} diff --git a/third_party/libobjc/opts/TypeFeedbackDrivenInliner.cpp b/third_party/libobjc/opts/TypeFeedbackDrivenInliner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a189f03c2b83c9da61588083b2ac45b00945da3e --- /dev/null +++ b/third_party/libobjc/opts/TypeFeedbackDrivenInliner.cpp @@ -0,0 +1,99 @@ +#include "llvm/Constants.h" +#include "llvm/Pass.h" +#include "llvm/Module.h" +#include "llvm/Function.h" +#include "llvm/Instructions.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Analysis/InlineCost.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Linker.h" +#include <vector> +#include "TypeInfoProvider.h" + +using namespace llvm; +using namespace GNUstep; + +namespace { + struct GNUObjCTypeFeedbackDrivenInliner : public ModulePass { + + typedef std::pair<CallInst*,CallInst*> callPair; + typedef std::vector<callPair > replacementVector; + static char ID; + uint32_t callsiteCount; + const IntegerType *Int32Ty; + + + public: + + GNUObjCTypeFeedbackDrivenInliner() : ModulePass(ID), callsiteCount(0) {} + + virtual bool runOnModule(Module &M) + { + bool modified = false; + LLVMContext &VMContext = M.getContext(); + Int32Ty = IntegerType::get(VMContext, 32); + TypeInfoProvider::CallSiteMap *SiteMap = + TypeInfoProvider::SharedTypeInfoProvider()->getCallSitesForModule(M); + SmallPtrSet<const Function *, 16> NeverInline; + + //TypeInfoProvider::SharedTypeInfoProvider()->PrintStatistics(); + GNUstep::IMPCacher cacher = GNUstep::IMPCacher(M.getContext(), this); + InlineCostAnalyzer CA; + SmallVector<CallSite, 16> messages; + + for (Module::iterator F=M.begin(), fend=M.end() ; + F != fend ; ++F) { + + + if (F->isDeclaration()) { continue; } + + for (Function::iterator i=F->begin(), end=F->end() ; + i != end ; ++i) { + for (BasicBlock::iterator b=i->begin(), last=i->end() ; + b != last ; ++b) { + CallSite call(b); + if (call.getInstruction() && !call.getCalledFunction()) { + messages.push_back(call); + } + } + } + } + TypeInfoProvider::CallSiteMap::iterator Entry = SiteMap->begin(); + + for (SmallVectorImpl<CallSite>::iterator i=messages.begin(), + e=messages.end() ; e!=i ; ++i, ++Entry) { + + if (Entry->size() == 1) { + + Function *method = M.getFunction(Entry->begin()->getKey()); + if (0 == method || method->isDeclaration()) { continue; } + +#if (LLVM_MAJOR > 3) || ((LLVM_MAJOR == 3) && (LLVM_MINOR > 0)) + InlineCost IC = CA.getInlineCost((*i), method, 200); +#else + InlineCost IC = CA.getInlineCost((*i), method, NeverInline); +#define getCost getValue +#endif + // FIXME: 200 is a random number. Pick a better one! + if (IC.isAlways() || (IC.isVariable() && IC.getCost() < 200)) { + cacher.SpeculativelyInline((*i).getInstruction(), method); + modified = true; + } + } + // FIXME: Inline the most popular call if one is much more popular + // than the others. + } + return modified; + } + + }; + + char GNUObjCTypeFeedbackDrivenInliner::ID = 0; + RegisterPass<GNUObjCTypeFeedbackDrivenInliner> X("gnu-objc-feedback-driven-inline", + "Objective-C type feedback-driven inliner for the GNU runtime.", false, + true); +} + +ModulePass *createTypeFeedbackDrivenInlinerPass(void) { + return new GNUObjCTypeFeedbackDrivenInliner(); +} diff --git a/third_party/libobjc/opts/TypeInfoProvider.h b/third_party/libobjc/opts/TypeInfoProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..cd659bf3c773ceab09638e3f84e75f856f0109f9 --- /dev/null +++ b/third_party/libobjc/opts/TypeInfoProvider.h @@ -0,0 +1,45 @@ +#include "IMPCacher.h" +#include "llvm/Pass.h" +#include "llvm/Function.h" +#include "llvm/Module.h" +#include "llvm/Instructions.h" +#include "llvm/Constants.h" +#include "llvm/LLVMContext.h" +#include "llvm/Metadata.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Support/CallSite.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/ADT/StringMap.h" + +using namespace llvm; +namespace GNUstep +{ + class TypeInfoProvider + { + public: + typedef StringMap<uintptr_t> CallSiteEntry; + //typedef std::vector<CallSiteEntry> CallSiteMap; + typedef SmallVector<CallSiteEntry, 16> CallSiteMap; + private: + struct callsite_info + { + uintptr_t moduleID; + int32_t callsiteID; + uintptr_t methodID; + }; + const char *symbol_table; + size_t symbol_size; + StringMap<CallSiteMap> CallSiteRecords; + + void loadCallsiteRecords(callsite_info *callsite_records, size_t size); + TypeInfoProvider(void); + + public: + CallSiteMap* getCallSitesForModule(Module &M); + void PrintStatistics(); + static TypeInfoProvider* SharedTypeInfoProvider(); + ~TypeInfoProvider(); + }; + +} diff --git a/third_party/libobjc/pool.h b/third_party/libobjc/pool.h new file mode 100644 index 0000000000000000000000000000000000000000..ffa8a6ad7cd27e718a33664d62e43e73c41cb031 --- /dev/null +++ b/third_party/libobjc/pool.h @@ -0,0 +1,53 @@ +#include <stdlib.h> +#include "lock.h" + +#ifndef POOL_TYPE +#error POOL_TYPE must be defined +#endif +#ifndef POOL_TYPE +#error POOL_NAME must be defined +#endif + +// Horrible multiple indirection to satisfy the weird precedence rules in cpp +#define REALLY_PREFIX_SUFFIX(x,y) x ## y +#define PREFIX_SUFFIX(x, y) REALLY_PREFIX_SUFFIX(x, y) +#define NAME(x) PREFIX_SUFFIX(POOL_NAME, x) + +#define PAGE_SIZE 4096 + +// Malloc one page at a time. +#define POOL_SIZE ((PAGE_SIZE) / sizeof(POOL_TYPE)) +static POOL_TYPE* NAME(_pool); +static int NAME(_pool_next_index) = -1; + +#ifdef THREAD_SAFE_POOL +static mutex_t NAME(_lock); +#define LOCK_POOL() LOCK(&POOL_NAME##_lock) +#define UNLOCK_POOL() LOCK(&POOL_NAME##_lock) +#else +#define LOCK_POOL() +#define UNLOCK_POOL() +#endif + +static inline POOL_TYPE*NAME(_pool_alloc)(void) +{ + LOCK_POOL(); + if (0 > NAME(_pool_next_index)) + { + NAME(_pool) = malloc(PAGE_SIZE); + NAME(_pool_next_index) = POOL_SIZE - 1; + } + POOL_TYPE* new = &NAME(_pool)[NAME(_pool_next_index)--]; + UNLOCK_POOL(); + return new; +} +#undef NAME +#undef POOL_SIZE +#undef PAGE_SIZE +#undef POOL_NAME +#undef POOL_TYPE +#undef LOCK_POOL +#undef UNLOCK_POOL +#ifdef THREAD_SAFE_POOL +#undef THREAD_SAFE_POOL +#endif diff --git a/third_party/libobjc/properties.h b/third_party/libobjc/properties.h new file mode 100644 index 0000000000000000000000000000000000000000..9500ac56eec51948852ec070be0279ee9f78091b --- /dev/null +++ b/third_party/libobjc/properties.h @@ -0,0 +1,105 @@ +#include "visibility.h" + +enum PropertyAttributeKind +{ + /** + * Property has no attributes. + */ + OBJC_PR_noattr = 0x00, + /** + * The property is declared read-only. + */ + OBJC_PR_readonly = (1<<0), + /** + * The property has a getter. + */ + OBJC_PR_getter = (1<<1), + /** + * The property has assign semantics. + */ + OBJC_PR_assign = (1<<2), + /** + * The property is declared read-write. + */ + OBJC_PR_readwrite = (1<<3), + /** + * Property has retain semantics. + */ + OBJC_PR_retain = (1<<4), + /** + * Property has copy semantics. + */ + OBJC_PR_copy = (1<<5), + /** + * Property is marked as non-atomic. + */ + OBJC_PR_nonatomic = (1<<6), + /** + * Property has setter. + */ + OBJC_PR_setter = (1<<7) +}; + +/** + * Structure used for property enumeration. Note that property enumeration is + * currently quite broken on OS X, so achieving full compatibility there is + * impossible. Instead, we strive to achieve compatibility with the + * documentation. + */ +struct objc_property +{ + /** + * Name of this property. + */ + const char *name; + /** + * Attributes for this property. Made by ORing together + * PropertyAttributeKinds. + */ + char attributes; + /** + * Flag set if the property is synthesized. + */ + const char isSynthesized; + /** + * Name of the getter for this property. + */ + const char *getter_name; + /** + * Type encoding for the get method for this property. + */ + const char *getter_types; + /** + * Name of the set method for this property. + */ + const char *setter_name; + /** + * Type encoding of the setter for this property. + */ + const char *setter_types; +}; + +/** + * List of property inrospection data. + */ +struct objc_property_list +{ + /** + * Number of properties in this array. + */ + int count; + /* + * The next property in a linked list. + */ + struct objc_property_list *next; + /** + * List of properties. + */ + struct objc_property properties[]; +}; + +/** + * Constructs a property description from a list of attributes. + */ +PRIVATE struct objc_property propertyFromAttrs(const objc_property_attribute_t *attributes, + unsigned int attributeCount); diff --git a/third_party/libobjc/properties.m b/third_party/libobjc/properties.m new file mode 100644 index 0000000000000000000000000000000000000000..fe4344d996bf187564f7320bdb3ed5521c455ffd --- /dev/null +++ b/third_party/libobjc/properties.m @@ -0,0 +1,532 @@ +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "class.h" +#include "properties.h" +#include "spinlock.h" +#include "visibility.h" +#include "nsobject.h" +#include "gc_ops.h" +#include "lock.h" + +PRIVATE int spinlocks[spinlock_count]; + +/** + * Public function for getting a property. + */ +id objc_getProperty(id obj, SEL _cmd, ptrdiff_t offset, BOOL isAtomic) +{ + if (nil == obj) { return nil; } + char *addr = (char*)obj; + addr += offset; + if (isGCEnabled) + { + return *(id*)addr; + } + id ret; + if (isAtomic) + { + volatile int *lock = lock_for_pointer(addr); + lock_spinlock(lock); + ret = *(id*)addr; + ret = objc_retain(ret); + unlock_spinlock(lock); + ret = objc_autoreleaseReturnValue(ret); + } + else + { + ret = *(id*)addr; + ret = objc_retainAutoreleaseReturnValue(ret); + } + return ret; +} + +void objc_setProperty(id obj, SEL _cmd, ptrdiff_t offset, id arg, BOOL isAtomic, BOOL isCopy) +{ + if (nil == obj) { return; } + char *addr = (char*)obj; + addr += offset; + + if (isGCEnabled) + { + if (isCopy) + { + arg = [arg copy]; + } + *(id*)addr = arg; + return; + } + if (isCopy) + { + arg = [arg copy]; + } + else + { + arg = objc_retain(arg); + } + id old; + if (isAtomic) + { + volatile int *lock = lock_for_pointer(addr); + lock_spinlock(lock); + old = *(id*)addr; + *(id*)addr = arg; + unlock_spinlock(lock); + } + else + { + old = *(id*)addr; + *(id*)addr = arg; + } + objc_release(old); +} + +/** + * Structure copy function. This is provided for compatibility with the Apple + * APIs (it's an ABI function, so it's semi-public), but it's a bad design so + * it's not used. The problem is that it does not identify which of the + * pointers corresponds to the object, which causes some excessive locking to + * be needed. + */ +void objc_copyPropertyStruct(void *dest, + void *src, + ptrdiff_t size, + BOOL atomic, + BOOL strong) +{ + if (atomic) + { + volatile int *lock = lock_for_pointer(src); + volatile int *lock2 = lock_for_pointer(src); + lock_spinlock(lock); + lock_spinlock(lock2); + memcpy(dest, src, size); + unlock_spinlock(lock); + unlock_spinlock(lock2); + } + else + { + memcpy(dest, src, size); + } +} + +/** + * Get property structure function. Copies a structure from an ivar to another + * variable. Locks on the address of src. + */ +void objc_getPropertyStruct(void *dest, + void *src, + ptrdiff_t size, + BOOL atomic, + BOOL strong) +{ + if (atomic) + { + volatile int *lock = lock_for_pointer(src); + lock_spinlock(lock); + memcpy(dest, src, size); + unlock_spinlock(lock); + } + else + { + memcpy(dest, src, size); + } +} + +/** + * Set property structure function. Copes a structure to an ivar. Locks on + * dest. + */ +void objc_setPropertyStruct(void *dest, + void *src, + ptrdiff_t size, + BOOL atomic, + BOOL strong) +{ + if (atomic) + { + volatile int *lock = lock_for_pointer(dest); + lock_spinlock(lock); + memcpy(dest, src, size); + unlock_spinlock(lock); + } + else + { + memcpy(dest, src, size); + } +} + + +objc_property_t class_getProperty(Class cls, const char *name) +{ + // Old ABI classes don't have declared properties + if (Nil == cls || !objc_test_class_flag(cls, objc_class_flag_new_abi)) + { + return NULL; + } + struct objc_property_list *properties = cls->properties; + while (NULL != properties) + { + for (int i=0 ; i<properties->count ; i++) + { + objc_property_t p = &properties->properties[i]; + if (strcmp(p->name, name) == 0) + { + return p; + } + } + properties = properties->next; + } + return NULL; +} + +objc_property_t* class_copyPropertyList(Class cls, unsigned int *outCount) +{ + if (Nil == cls || !objc_test_class_flag(cls, objc_class_flag_new_abi)) + { + if (NULL != outCount) { *outCount = 0; } + return NULL; + } + struct objc_property_list *properties = cls->properties; + unsigned int count = 0; + for (struct objc_property_list *l=properties ; NULL!=l ; l=l->next) + { + count += l->count; + } + if (NULL != outCount) + { + *outCount = count; + } + if (0 == count) + { + return NULL; + } + objc_property_t *list = calloc(sizeof(objc_property_t), count); + unsigned int out = 0; + for (struct objc_property_list *l=properties ; NULL!=l ; l=l->next) + { + for (int i=0 ; i<properties->count ; i++) + { + list[out] = &l->properties[i]; + } + } + return list; +} + +const char *property_getName(objc_property_t property) +{ + if (NULL == property) { return NULL; } + + const char *name = property->name; + if (name[0] == 0) + { + name += name[1]; + } + return name; +} + +PRIVATE size_t lengthOfTypeEncoding(const char *types); + +/** + * The compiler stores the type encoding of the getter. We replace this with + * the type encoding of the property itself. We use a 0 byte at the start to + * indicate that the swap has taken place. + */ +static const char *property_getTypeEncoding(objc_property_t property) +{ + if (NULL == property) { return NULL; } + + const char *name = property->getter_types; + if (name[0] == 0) + { + return &name[1]; + } + size_t typeSize = lengthOfTypeEncoding(name); + char *buffer = malloc(typeSize + 2); + buffer[0] = 0; + memcpy(buffer+1, name, typeSize); + buffer[typeSize+1] = 0; + if (!__sync_bool_compare_and_swap(&(property->getter_types), name, buffer)) + { + free(buffer); + } + return &property->getter_types[1]; +} + +const char *property_getAttributes(objc_property_t property) +{ + if (NULL == property) { return NULL; } + + const char *name = (char*)property->name; + if (name[0] == 0) + { + return name + 2; + } + + const char *typeEncoding = property_getTypeEncoding(property); + size_t typeSize = strlen(typeEncoding); + size_t nameSize = strlen(property->name); + // Encoding is T{type},V{name}, so 4 bytes for the "T,V" that we always + // need. We also need two bytes for the leading null and the length. + size_t encodingSize = typeSize + nameSize + 6; + char flags[16]; + size_t i = 0; + // Flags that are a comma then a character + if ((property->attributes & OBJC_PR_readonly) == OBJC_PR_readonly) + { + flags[i++] = ','; + flags[i++] = 'R'; + } + if ((property->attributes & OBJC_PR_copy) == OBJC_PR_copy) + { + flags[i++] = ','; + flags[i++] = 'C'; + } + if ((property->attributes & OBJC_PR_retain) == OBJC_PR_retain) + { + flags[i++] = ','; + flags[i++] = '&'; + } + if ((property->attributes & OBJC_PR_nonatomic) == OBJC_PR_nonatomic) + { + flags[i++] = ','; + flags[i++] = 'N'; + } + encodingSize += i; + flags[i] = '\0'; + size_t setterLength = 0; + size_t getterLength = 0; + if ((property->attributes & OBJC_PR_getter) == OBJC_PR_getter) + { + getterLength = strlen(property->getter_name); + encodingSize += 2 + getterLength; + } + if ((property->attributes & OBJC_PR_setter) == OBJC_PR_setter) + { + setterLength = strlen(property->setter_name); + encodingSize += 2 + setterLength; + } + unsigned char *encoding = malloc(encodingSize); + // Set the leading 0 and the offset of the name + unsigned char *insert = encoding; + *(insert++) = 0; + *(insert++) = 0; + // Set the type encoding + *(insert++) = 'T'; + memcpy(insert, typeEncoding, typeSize); + insert += typeSize; + // Set the flags + memcpy(insert, flags, i); + insert += i; + if ((property->attributes & OBJC_PR_getter) == OBJC_PR_getter) + { + *(insert++) = ','; + *(insert++) = 'G'; + memcpy(insert, property->getter_name, getterLength); + insert += getterLength; + } + if ((property->attributes & OBJC_PR_setter) == OBJC_PR_setter) + { + *(insert++) = ','; + *(insert++) = 'S'; + memcpy(insert, property->setter_name, setterLength); + insert += setterLength; + } + *(insert++) = ','; + *(insert++) = 'V'; + encoding[1] = (unsigned char)(uintptr_t)(insert - encoding); + memcpy(insert, property->name, nameSize); + insert += nameSize; + *insert = '\0'; + // If another thread installed the encoding string while we were computing + // it, then discard the one that we created and return theirs. + if (!__sync_bool_compare_and_swap(&(property->name), name, (char*)encoding)) + { + free(encoding); + return property->name + 2; + } + return (const char*)(encoding + 2); +} + +objc_property_attribute_t *property_copyAttributeList(objc_property_t property, + unsigned int *outCount) +{ + if (NULL == property) { return NULL; } + objc_property_attribute_t attrs[10]; + int count = 0; + + attrs[count].name = "T"; + attrs[count].value = property_getTypeEncoding(property); + count++; + if ((property->attributes & OBJC_PR_copy) == OBJC_PR_copy) + { + attrs[count].name = "C"; + attrs[count].value = ""; + count++; + } + if ((property->attributes & OBJC_PR_retain) == OBJC_PR_retain) + { + attrs[count].name = "&"; + attrs[count].value = ""; + count++; + } + if ((property->attributes & OBJC_PR_nonatomic) == OBJC_PR_nonatomic) + { + attrs[count].name = "N"; + attrs[count].value = ""; + count++; + } + if ((property->attributes & OBJC_PR_getter) == OBJC_PR_getter) + { + attrs[count].name = "G"; + attrs[count].value = property->getter_name; + count++; + } + if ((property->attributes & OBJC_PR_setter) == OBJC_PR_setter) + { + attrs[count].name = "S"; + attrs[count].value = property->setter_name; + count++; + } + attrs[count].name = "V"; + attrs[count].value = property_getName(property); + count++; + + objc_property_attribute_t *propAttrs = calloc(sizeof(objc_property_attribute_t), count); + memcpy(propAttrs, attrs, count * sizeof(objc_property_attribute_t)); + if (NULL != outCount) + { + *outCount = count; + } + return propAttrs; +} + +PRIVATE struct objc_property propertyFromAttrs(const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + struct objc_property p = { 0 }; + for (unsigned int i=0 ; i<attributeCount ; i++) + { + switch (attributes[i].name[0]) + { + case 'T': + { + size_t typeSize = strlen(attributes[i].value); + char *buffer = malloc(typeSize + 2); + buffer[0] = 0; + memcpy(buffer+1, attributes[i].value, typeSize); + buffer[typeSize+1] = 0; + p.getter_types = buffer; + break; + } + case 'S': + { + p.setter_name = strdup(attributes[i].value); + break; + } + case 'G': + { + p.getter_name = strdup(attributes[i].value); + break; + } + case 'V': + { + p.name = strdup(attributes[i].value); + break; + } + case 'C': + { + p.attributes |= OBJC_PR_copy; + } + case '&': + { + p.attributes |= OBJC_PR_retain; + } + case 'N': + { + p.attributes |= OBJC_PR_nonatomic; + } + } + } + return p; +} + +BOOL class_addProperty(Class cls, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + if ((Nil == cls) || (NULL == name) || (class_getProperty(cls, name) != 0)) { return NO; } + struct objc_property p = propertyFromAttrs(attributes, attributeCount); + // If there is a name mismatch, the attributes are invalid. + if ((p.name != 0) && (strcmp(name, p.name) != 0)) { return NO; } + + struct objc_property_list *l = calloc(1, sizeof(struct objc_property_list) + + sizeof(struct objc_property)); + l->count = 0; + memcpy(&l->properties, &p, sizeof(struct objc_property)); + LOCK_RUNTIME_FOR_SCOPE(); + l->next = cls->properties; + cls->properties = l; + return YES; +} + +void class_replaceProperty(Class cls, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + if ((Nil == cls) || (NULL == name)) { return; } + objc_property_t old = class_getProperty(cls, name); + if (NULL == old) + { + class_addProperty(cls, name, attributes, attributeCount); + return; + } + struct objc_property p = propertyFromAttrs(attributes, attributeCount); + memcpy(old, &p, sizeof(struct objc_property)); + if (NULL == old->name) + { + old->name = name; + } +} +char *property_copyAttributeValue(objc_property_t property, + const char *attributeName) +{ + if ((NULL == property) || (NULL == attributeName)) { return NULL; } + switch (attributeName[0]) + { + case 'T': + { + return strdup(property_getTypeEncoding(property)); + } + case 'V': + { + return strdup(property_getName(property)); + } + case 'S': + { + return strdup(property->setter_name); + } + case 'G': + { + return strdup(property->getter_name); + } + case 'C': + { + return ((property->attributes |= OBJC_PR_copy) == OBJC_PR_copy) ? strdup("") : 0; + } + case '&': + { + return ((property->attributes |= OBJC_PR_retain) == OBJC_PR_retain) ? strdup("") : 0; + } + case 'N': + { + return ((property->attributes |= OBJC_PR_nonatomic) == OBJC_PR_nonatomic) ? strdup("") : 0; + } + } + return 0; +} diff --git a/third_party/libobjc/protocol.c b/third_party/libobjc/protocol.c new file mode 100644 index 0000000000000000000000000000000000000000..e9b80ce93cf2fe7e3714d405738615c3833a42d8 --- /dev/null +++ b/third_party/libobjc/protocol.c @@ -0,0 +1,616 @@ +#include "objc/runtime.h" +#include "protocol.h" +#include "properties.h" +#include "class.h" +#include "lock.h" +#include <stdlib.h> + +#define BUFFER_TYPE struct objc_protocol_list +#include "buffer.h" + +// Get the functions for string hashing +#include "string_hash.h" + +static int protocol_compare(const char *name, + const struct objc_protocol2 *protocol) +{ + return string_compare(name, protocol->name); +} +static int protocol_hash(const struct objc_protocol2 *protocol) +{ + return string_hash(protocol->name); +} +#define MAP_TABLE_NAME protocol +#define MAP_TABLE_COMPARE_FUNCTION protocol_compare +#define MAP_TABLE_HASH_KEY string_hash +#define MAP_TABLE_HASH_VALUE protocol_hash +#include "hash_table.h" + +static protocol_table *known_protocol_table; + +void init_protocol_table(void) +{ + protocol_initialize(&known_protocol_table, 128); +} + +static void protocol_table_insert(const struct objc_protocol2 *protocol) +{ + protocol_insert(known_protocol_table, (void*)protocol); +} + +struct objc_protocol2 *protocol_for_name(const char *name) +{ + return protocol_table_get(known_protocol_table, name); +} + +static id ObjC2ProtocolClass = 0; + +static int isEmptyProtocol(struct objc_protocol2 *aProto) +{ + int isEmpty = + ((aProto->instance_methods == NULL) || + (aProto->instance_methods->count == 0)) && + ((aProto->class_methods == NULL) || + (aProto->class_methods->count == 0)) && + ((aProto->protocol_list == NULL) || + (aProto->protocol_list->count == 0)); + if (aProto->isa == ObjC2ProtocolClass) + { + struct objc_protocol2 *p2 = (struct objc_protocol2*)aProto; + isEmpty &= (p2->optional_instance_methods->count == 0); + isEmpty &= (p2->optional_class_methods->count == 0); + isEmpty &= (p2->properties == 0) || (p2->properties->count == 0); + isEmpty &= (p2->optional_properties == 0) || (p2->optional_properties->count == 0); + } + return isEmpty; +} + +// FIXME: Make p1 adopt all of the stuff in p2 +static void makeProtocolEqualToProtocol(struct objc_protocol2 *p1, + struct objc_protocol2 *p2) +{ +#define COPY(x) p1->x = p2->x + COPY(instance_methods); + COPY(class_methods); + COPY(protocol_list); + if (p1->isa == ObjC2ProtocolClass && + p2->isa == ObjC2ProtocolClass) + { + COPY(optional_instance_methods); + COPY(optional_class_methods); + COPY(properties); + COPY(optional_properties); + } +#undef COPY +} + +static struct objc_protocol2 *unique_protocol(struct objc_protocol2 *aProto) +{ + if (ObjC2ProtocolClass == 0) + { + ObjC2ProtocolClass = objc_getClass("Protocol2"); + } + struct objc_protocol2 *oldProtocol = + protocol_for_name(aProto->name); + if (NULL == oldProtocol) + { + // This is the first time we've seen this protocol, so add it to the + // hash table and ignore it. + protocol_table_insert(aProto); + return aProto; + } + if (isEmptyProtocol(oldProtocol)) + { + if (isEmptyProtocol(aProto)) + { + return aProto; + // Add protocol to a list somehow. + } + else + { + // This protocol is not empty, so we use its definitions + makeProtocolEqualToProtocol(oldProtocol, aProto); + return aProto; + } + } + else + { + if (isEmptyProtocol(aProto)) + { + makeProtocolEqualToProtocol(aProto, oldProtocol); + return oldProtocol; + } + else + { + return oldProtocol; + //FIXME: We should really perform a check here to make sure the + //protocols are actually the same. + } + } +} + +static id protocol_class; +static id protocol_class2; +enum protocol_version +{ + /** + * Legacy (GCC-compatible) protocol version. + */ + protocol_version_legacy = 2, + /** + * New (Objective-C 2-compatible) protocol version. + */ + protocol_version_objc2 = 3 +}; + +static BOOL init_protocols(struct objc_protocol_list *protocols) +{ + // Protocol2 is a subclass of Protocol, so if we have loaded Protocol2 we + // must have also loaded Protocol. + if (nil == protocol_class2) + { + protocol_class = objc_getClass("Protocol"); + protocol_class2 = objc_getClass("Protocol2"); + } + if (nil == protocol_class2 || nil == protocol_class) + { + return NO; + } + + for (unsigned i=0 ; i<protocols->count ; i++) + { + struct objc_protocol2 *aProto = protocols->list[i]; + // Don't initialise a protocol twice + if (aProto->isa == protocol_class || + aProto->isa == protocol_class2) { continue ;} + + // Protocols in the protocol list have their class pointers set to the + // version of the protocol class that they expect. + enum protocol_version version = + (enum protocol_version)(uintptr_t)aProto->isa; + switch (version) + { + default: + fprintf(stderr, "Unknown protocol version"); + abort(); + case protocol_version_legacy: + aProto->isa = protocol_class; + break; + case protocol_version_objc2: + aProto->isa = protocol_class2; + break; + } + // Initialize all of the protocols that this protocol refers to + if (NULL != aProto->protocol_list) + { + init_protocols(aProto->protocol_list); + } + // Replace this protocol with a unique version of it. + protocols->list[i] = unique_protocol(aProto); + } + return YES; +} + +PRIVATE void objc_init_protocols(struct objc_protocol_list *protocols) +{ + if (!init_protocols(protocols)) + { + set_buffered_object_at_index(protocols, buffered_objects++); + return; + } + if (buffered_objects > 0) { return; } + + // If we can load one protocol, then we can load all of them. + for (unsigned i=0 ; i<buffered_objects ; i++) + { + struct objc_protocol_list *c = buffered_object_at_index(i); + if (NULL != c) + { + init_protocols(c); + set_buffered_object_at_index(NULL, i); + } + } + compact_buffer(); +} + +// Public functions: +Protocol *objc_getProtocol(const char *name) +{ + if (NULL == name) { return NULL; } + return (Protocol*)protocol_for_name(name); +} + +BOOL protocol_conformsToProtocol(Protocol *p1, Protocol *p2) +{ + if (NULL == p1 || NULL == p2) { return NO; } + + // A protocol trivially conforms to itself + if (strcmp(p1->name, p2->name) == 0) { return YES; } + + for (struct objc_protocol_list *list = p1->protocol_list ; + list != NULL ; list = list->next) + { + for (int i=0 ; i<list->count ; i++) + { + if (strcmp(list->list[i]->name, p2->name) == 0) + { + return YES; + } + if (protocol_conformsToProtocol((Protocol*)list->list[i], p2)) + { + return YES; + } + } + } + return NO; +} + +BOOL class_conformsToProtocol(Class cls, Protocol *protocol) +{ + if (Nil == cls || NULL == protocol) { return NO; } + for ( ; Nil != cls ; cls = class_getSuperclass(cls)) + { + for (struct objc_protocol_list *protocols = cls->protocols; + protocols != NULL ; protocols = protocols->next) + { + for (int i=0 ; i<protocols->count ; i++) + { + Protocol *p1 = (Protocol*)protocols->list[i]; + if (protocol_conformsToProtocol(p1, protocol)) + { + return YES; + } + } + } + } + return NO; +} + +static struct objc_method_description_list * +get_method_list(Protocol *p, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + static id protocol2 = NULL; + + if (NULL == protocol2) + { + protocol2 = objc_getClass("Protocol2"); + } + struct objc_method_description_list *list; + if (isRequiredMethod) + { + if (isInstanceMethod) + { + list = p->instance_methods; + } + else + { + list = p->class_methods; + } + } + else + { + if (p->isa != protocol2) { return NULL; } + + + if (isInstanceMethod) + { + list = ((Protocol2*)p)->optional_instance_methods; + } + else + { + list = ((Protocol2*)p)->optional_class_methods; + } + } + return list; +} + +struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, + BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *count) +{ + if (NULL == p) { return NULL; } + struct objc_method_description_list *list = + get_method_list(p, isRequiredMethod, isInstanceMethod); + *count = 0; + if (NULL == list || list->count == 0) { return NULL; } + + *count = list->count; + struct objc_method_description *out = + calloc(sizeof(struct objc_method_description_list), list->count); + + for (int i=0 ; i<list->count ; i++) + { + out[i].name = sel_registerTypedName_np(list->methods[i].name, + list->methods[i].types); + out[i].types = list->methods[i].types; + } + return out; +} + +Protocol*__unsafe_unretained* protocol_copyProtocolList(Protocol *p, unsigned int *count) +{ + if (NULL == p) { return NULL; } + *count = 0; + if (p->protocol_list == NULL || p->protocol_list->count ==0) + { + return NULL; + } + + Protocol **out = calloc(sizeof(Protocol*), p->protocol_list->count); + for (int i=0 ; i<p->protocol_list->count ; i++) + { + out[i] = (Protocol*)p->protocol_list->list[i]; + } + return NULL; +} + +objc_property_t *protocol_copyPropertyList(Protocol *protocol, + unsigned int *outCount) +{ + if (NULL == protocol) { return NULL; } + if (protocol->isa != ObjC2ProtocolClass) + { + return NULL; + } + Protocol2 *p = (Protocol2*)protocol; + struct objc_property_list *properties = p->properties; + unsigned int count = 0; + if (NULL != properties) + { + count = properties->count; + } + if (NULL != p->optional_properties) + { + count = p->optional_properties->count; + } + if (0 == count) + { + return NULL; + } + objc_property_t *list = calloc(sizeof(objc_property_t), count); + unsigned int out = 0; + if (properties) + { + for (int i=0 ; i<properties->count ; i++) + { + list[out] = &properties->properties[i]; + } + } + properties = p->optional_properties; + if (properties) + { + for (int i=0 ; i<properties->count ; i++) + { + list[out] = &properties->properties[i]; + } + } + *outCount = count; + return list; +} + +objc_property_t protocol_getProperty(Protocol *protocol, + const char *name, + BOOL isRequiredProperty, + BOOL isInstanceProperty) +{ + if (NULL == protocol) { return NULL; } + // Class properties are not supported yet (there is no language syntax for + // defining them!) + if (!isInstanceProperty) { return NULL; } + if (protocol->isa != ObjC2ProtocolClass) + { + return NULL; + } + Protocol2 *p = (Protocol2*)protocol; + struct objc_property_list *properties = + isRequiredProperty ? p->properties : p->optional_properties; + while (NULL != properties) + { + for (int i=0 ; i<properties->count ; i++) + { + objc_property_t prop = &properties->properties[i]; + if (strcmp(prop->name, name) == 0) + { + return prop; + } + } + properties = properties->next; + } + return NULL; +} + + +struct objc_method_description +protocol_getMethodDescription(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_method_description d = {0,0}; + struct objc_method_description_list *list = + get_method_list(p, isRequiredMethod, isInstanceMethod); + if (NULL == list) + { + return d; + } + // TODO: We could make this much more efficient if + for (int i=0 ; i<list->count ; i++) + { + SEL s = sel_registerTypedName_np(list->methods[i].name, 0); + if (sel_isEqual(s, aSel)) + { + d.name = s; + d.types = list->methods[i].types; + break; + } + } + return d; +} + + +const char *protocol_getName(Protocol *p) +{ + if (NULL != p) + { + return p->name; + } + return NULL; +} + +BOOL protocol_isEqual(Protocol *p, Protocol *other) +{ + if (NULL == p || NULL == other) + { + return NO; + } + if (p == other || + p->name == other->name || + 0 == strcmp(p->name, other->name)) + { + return YES; + } + return NO; +} + +Protocol*__unsafe_unretained* objc_copyProtocolList(unsigned int *outCount) +{ + unsigned int total = known_protocol_table->table_used; + Protocol **p = calloc(sizeof(Protocol*), known_protocol_table->table_used); + + struct protocol_table_enumerator *e = NULL; + Protocol *next; + + unsigned int count = 0; + while ((count < total) && (next = protocol_next(known_protocol_table, &e))) + { + p[count++] = next; + } + if (NULL != outCount) + { + *outCount = total; + } + return p; +} + + +Protocol *objc_allocateProtocol(const char *name) +{ + if (objc_getProtocol(name) != NULL) { return NULL; } + Protocol *p = calloc(1, sizeof(Protocol2)); + p->name = strdup(name); + return p; +} +void objc_registerProtocol(Protocol *proto) +{ + if (NULL == proto) { return; } + LOCK_RUNTIME_FOR_SCOPE(); + if (objc_getProtocol(proto->name) != NULL) { return; } + if (nil != proto->isa) { return; } + proto->isa = ObjC2ProtocolClass; + protocol_table_insert((struct objc_protocol2*)proto); +} +void protocol_addMethodDescription(Protocol *aProtocol, + SEL name, + const char *types, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + if ((NULL == aProtocol) || (NULL == name) || (NULL == types)) { return; } + if (nil != aProtocol->isa) { return; } + Protocol2 *proto = (Protocol2*)aProtocol; + struct objc_method_description_list **listPtr; + if (isInstanceMethod) + { + if (isRequiredMethod) + { + listPtr = &proto->instance_methods; + } + else + { + listPtr = &proto->optional_instance_methods; + } + } + else + { + if (isRequiredMethod) + { + listPtr = &proto->class_methods; + } + else + { + listPtr = &proto->optional_class_methods; + } + } + if (NULL == *listPtr) + { + *listPtr = calloc(1, sizeof(struct objc_method_description_list) + sizeof(struct objc_method_description)); + (*listPtr)->count = 1; + } + else + { + (*listPtr)->count++; + *listPtr = realloc(*listPtr, sizeof(struct objc_method_description_list) + + sizeof(struct objc_method_description) * (*listPtr)->count); + } + struct objc_method_description_list *list = *listPtr; + int index = list->count-1; + list->methods[index].name = sel_getName(name); + list->methods[index].types= types; +} +void protocol_addProtocol(Protocol *aProtocol, Protocol *addition) +{ + if ((NULL == aProtocol) || (NULL == addition)) { return; } + Protocol2 *proto = (Protocol2*)aProtocol; + if (NULL == proto->protocol_list) + { + proto->protocol_list = calloc(1, sizeof(struct objc_property_list) + sizeof(Protocol2*)); + proto->protocol_list->count = 1; + } + else + { + proto->protocol_list->count++; + proto->protocol_list = realloc(proto->protocol_list, sizeof(struct objc_property_list) + + proto->protocol_list->count * sizeof(Protocol2*)); + proto->protocol_list->count = 1; + } + proto->protocol_list->list[proto->protocol_list->count-1] = (Protocol2*)addition; +} +void protocol_addProperty(Protocol *aProtocol, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount, + BOOL isRequiredProperty, + BOOL isInstanceProperty) +{ + if ((NULL == aProtocol) || (NULL == name)) { return; } + if (nil != aProtocol->isa) { return; } + if (!isInstanceProperty) { return; } + Protocol2 *proto = (Protocol2*)aProtocol; + struct objc_property_list **listPtr; + if (isRequiredProperty) + { + listPtr = &proto->properties; + } + else + { + listPtr = &proto->optional_properties; + } + if (NULL == *listPtr) + { + *listPtr = calloc(1, sizeof(struct objc_property_list) + sizeof(struct objc_property)); + (*listPtr)->count = 1; + } + else + { + (*listPtr)->count++; + *listPtr = realloc(*listPtr, sizeof(struct objc_property_list) + + sizeof(struct objc_property) * (*listPtr)->count); + } + struct objc_property_list *list = *listPtr; + int index = list->count-1; + struct objc_property p = propertyFromAttrs(attributes, attributeCount); + p.name = strdup(name); + memcpy(&(list->properties[index]), &p, sizeof(p)); +} + diff --git a/third_party/libobjc/protocol.h b/third_party/libobjc/protocol.h new file mode 100644 index 0000000000000000000000000000000000000000..d44cd3c4468deb3de5284783e83cd3829458b82f --- /dev/null +++ b/third_party/libobjc/protocol.h @@ -0,0 +1,121 @@ +#include "selector.h" +#include <stdlib.h> + +struct objc_method_description_list +{ + /** + * Number of method descriptions in this list. + */ + int count; + /** + * Methods in this list. Note: these selectors are NOT resolved. The name + * field points to the name, not to the index of the uniqued version of the + * name. You must not use them for dispatch. + */ + struct objc_selector methods[]; +}; + + +#ifdef __OBJC__ +@interface Object { id isa; } @end +/** + * Definition of the Protocol type. Protocols are objects, but are rarely used + * as such. + */ +@interface Protocol : Object +{ + @public +#else +struct objc_protocol +{ + /** Class pointer. */ + id isa; +#endif + /** + * The name of this protocol. Two protocols are regarded as identical if + * they have the same name. + */ + char *name; + /** + * The list of protocols that this protocol conforms to. + */ + struct objc_protocol_list *protocol_list; + /** + * List of instance methods required by this protocol. + */ + struct objc_method_description_list *instance_methods; + /** + * List of class methods required by this protocol. + */ + struct objc_method_description_list *class_methods; +} +#ifdef __OBJC__ +@end +#else +; +#endif + +#ifdef __OBJC__ +@interface Protocol2 : Protocol +{ + @public +#else +typedef struct objc_protocol2 +{ + /** + * Redefinition of the superclass ivars in the C version. + */ + id isa; + char *name; + struct objc_protocol_list *protocol_list; + struct objc_method_description_list *instance_methods; + struct objc_method_description_list *class_methods; +#endif + /** + * Instance methods that are declared as optional for this protocol. + */ + struct objc_method_description_list *optional_instance_methods; + /** + * Class methods that are declared as optional for this protocol. + */ + struct objc_method_description_list *optional_class_methods; + /** + * Properties that are required by this protocol. + */ + struct objc_property_list *properties; + /** + * Optional properties. + */ + struct objc_property_list *optional_properties; +} +#ifdef __OBJC__ +@end +#else +Protocol2; +#endif + +/** + * List of protocols. Attached to a class or a category by the compiler and to + * a class by the runtime. + */ +struct objc_protocol_list +{ + /** + * Additional protocol lists. Loading a category that declares protocols + * will cause a new list to be prepended using this pointer to the protocol + * list for the class. Unlike methods, protocols can not be overridden, + * although it is possible for a protocol to appear twice. + */ + struct objc_protocol_list *next; + /** + * The number of protocols in this list. + */ + size_t count; + /** + * An array of protocols. Actually contains count elements, not 1. + * + * The instances in this array may be any version of protocols. + */ + Protocol2 *list[]; +}; + diff --git a/third_party/libobjc/runtime.c b/third_party/libobjc/runtime.c new file mode 100644 index 0000000000000000000000000000000000000000..627be9a237c3d24839ca2f121a8352212e4161ab --- /dev/null +++ b/third_party/libobjc/runtime.c @@ -0,0 +1,786 @@ +#include "objc/runtime.h" +#include "selector.h" +#include "class.h" +#include "protocol.h" +#include "ivar.h" +#include "method_list.h" +#include "lock.h" +#include "dtable.h" +#include "gc_ops.h" + +/* Make glibc export strdup() */ + +#if defined __GLIBC__ + #define __USE_BSD 1 +#endif + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +struct objc_slot *objc_get_slot(Class cls, SEL selector); +#define CHECK_ARG(arg) if (0 == arg) { return 0; } + +/** + * 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); + + while (cls) + { + struct objc_slot *slot = objc_get_slot(cls, cxx_destruct); + cls = Nil; + if (NULL != slot) + { + cls = slot->owner->super_class; + slot->method(obj, cxx_destruct); + } + } +} + +static void call_cxx_construct_for_class(Class cls, id obj) +{ + static SEL cxx_construct; + if (NULL == cxx_construct) + { + cxx_construct = sel_registerName(".cxx_contruct"); + } + struct objc_slot *slot = objc_get_slot(cls, cxx_construct); + if (NULL != slot) + { + cls = slot->owner->super_class; + if (Nil != cls) + { + call_cxx_construct_for_class(cls, obj); + } + slot->method(obj, cxx_construct); + } +} + +PRIVATE void call_cxx_construct(id obj) +{ + call_cxx_construct_for_class(classForObject(obj), obj); +} + +/** + * Looks up the instance method in a specific class, without recursing into + * superclasses. + */ +static Method class_getInstanceMethodNonrecursive(Class aClass, SEL aSelector) +{ + for (struct objc_method_list *methods = aClass->methods; + methods != NULL ; methods = methods->next) + { + for (int i=0 ; i<methods->count ; i++) + { + Method method = &methods->methods[i]; + if (sel_isEqual(method->selector, aSelector)) + { + return method; + } + } + } + return NULL; +} + +static void objc_updateDtableForClassContainingMethod(Method m) +{ + Class nextClass = Nil; + void *state = NULL; + SEL sel = method_getName(m); + while (Nil != (nextClass = objc_next_class(&state))) + { + if (class_getInstanceMethodNonrecursive(nextClass, sel) == m) + { + objc_update_dtable_for_class(nextClass); + return; + } + } +} + +BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, + const char *types) +{ + CHECK_ARG(cls); + CHECK_ARG(name); + CHECK_ARG(types); + // You can't add ivars to initialized classes. Note: We can't use the + // resolved flag here because class_getInstanceVariable() sets it. + if (objc_test_class_flag(cls, objc_class_flag_initialized)) + { + return NO; + } + + if (class_getInstanceVariable(cls, name) != NULL) + { + return NO; + } + + struct objc_ivar_list *ivarlist = cls->ivars; + + if (NULL == ivarlist) + { + cls->ivars = malloc(sizeof(struct objc_ivar_list) + sizeof(struct objc_ivar)); + cls->ivars->count = 1; + } + else + { + ivarlist->count++; + // objc_ivar_list contains one ivar. Others follow it. + cls->ivars = realloc(ivarlist, sizeof(struct objc_ivar_list) + + (ivarlist->count) * sizeof(struct objc_ivar)); + } + Ivar ivar = &cls->ivars->ivar_list[cls->ivars->count - 1]; + ivar->name = strdup(name); + ivar->type = strdup(types); + // Round up the offset of the ivar so it is correctly aligned. + long offset = cls->instance_size >> alignment; + + if (offset << alignment != cls->instance_size) + { + offset++; + } + offset <<= alignment; + + ivar->offset = offset; + // Increase the instance size to make space for this. + cls->instance_size = ivar->offset + size; + return YES; +} + +BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) +{ + CHECK_ARG(cls); + CHECK_ARG(name); + CHECK_ARG(imp); + CHECK_ARG(types); + const char *methodName = sel_getName(name); + struct objc_method_list *methods; + for (methods=cls->methods; methods!=NULL ; methods=methods->next) + { + for (int i=0 ; i<methods->count ; i++) + { + Method method = &methods->methods[i]; + if (strcmp(sel_getName(method->selector), methodName) == 0) + { + return NO; + } + } + } + + methods = malloc(sizeof(struct objc_method_list) + sizeof(struct objc_method)); + methods->next = cls->methods; + cls->methods = methods; + + methods->count = 1; + methods->methods[0].selector = sel_registerTypedName_np(methodName, types); + methods->methods[0].types = strdup(types); + methods->methods[0].imp = imp; + + if (objc_test_class_flag(cls, objc_class_flag_resolved)) + { + add_method_list_to_class(cls, methods); + } + + return YES; +} + +BOOL class_addProtocol(Class cls, Protocol *protocol) +{ + CHECK_ARG(cls); + CHECK_ARG(protocol); + if (class_conformsToProtocol(cls, protocol)) { return NO; } + struct objc_protocol_list *protocols = + malloc(sizeof(struct objc_protocol_list) + sizeof(Protocol2*)); + if (protocols == NULL) { return NO; } + protocols->next = cls->protocols; + protocols->count = 1; + protocols->list[0] = (Protocol2*)protocol; + cls->protocols = protocols; + + return YES; +} + +Ivar * class_copyIvarList(Class cls, unsigned int *outCount) +{ + CHECK_ARG(cls); + struct objc_ivar_list *ivarlist = NULL; + unsigned int count = 0; + unsigned int index; + Ivar *list; + + if (Nil != cls) + { + ivarlist = cls->ivars; + } + if (ivarlist != NULL) + { + count = ivarlist->count; + } + if (outCount != NULL) + { + *outCount = count; + } + if (count == 0) + { + return NULL; + } + + list = malloc((count + 1) * sizeof(struct objc_ivar *)); + list[count] = NULL; + count = 0; + for (index = 0; index < ivarlist->count; index++) + { + list[count++] = &ivarlist->ivar_list[index]; + } + + return list; +} + +Method * class_copyMethodList(Class cls, unsigned int *outCount) +{ + CHECK_ARG(cls); + unsigned int count = 0; + Method *list; + struct objc_method_list *methods; + + if (cls != NULL) + { + for (methods = cls->methods; methods != NULL; methods = methods->next) + { + count += methods->count; + } + } + if (outCount != NULL) + { + *outCount = count; + } + if (count == 0) + { + return NULL; + } + + list = malloc((count + 1) * sizeof(struct objc_method *)); + list[count] = NULL; + count = 0; + for (methods = cls->methods; methods != NULL; methods = methods->next) + { + unsigned int index; + for (index = 0; index < methods->count; index++) + { + list[count++] = &methods->methods[index]; + } + } + + return list; +} + +Protocol*__unsafe_unretained* class_copyProtocolList(Class cls, unsigned int *outCount) +{ + CHECK_ARG(cls); + struct objc_protocol_list *protocolList = NULL; + struct objc_protocol_list *list; + unsigned int count = 0; + Protocol **protocols; + + if (Nil != cls) + { + protocolList = cls->protocols; + } + for (list = protocolList; list != NULL; list = list->next) + { + count += list->count; + } + if (outCount != NULL) + { + *outCount = count; + } + if (count == 0) + { + return NULL; + } + + protocols = malloc((count + 1) * sizeof(Protocol *)); + protocols[count] = NULL; + count = 0; + for (list = protocolList; list != NULL; list = list->next) + { + memcpy(&protocols[count], list->list, list->count * sizeof(Protocol *)); + count += list->count; + } + return protocols; +} + +id class_createInstance(Class cls, size_t extraBytes) +{ + CHECK_ARG(cls); + if (sizeof(id) == 4) + { + if (cls == SmallObjectClasses[0]) + { + return (id)1; + } + } + else + { + for (int i=0 ; i<4 ; i++) + { + if (cls == SmallObjectClasses[i]) + { + return (id)(uintptr_t)((i<<1)+1); + } + } + } + + if (Nil == cls) { return nil; } + id obj = gc->allocate_class(cls, extraBytes); + obj->isa = cls; + call_cxx_construct(obj); + return obj; +} + +id object_copy(id obj, size_t size) +{ + Class cls = object_getClass(obj); + id cpy = class_createInstance(cls, size - class_getInstanceSize(cls)); + memcpy(((char*)cpy + sizeof(id)), ((char*)obj + sizeof(id)), size - sizeof(id)); + return cpy; +} + +id object_dispose(id obj) +{ + call_cxx_destruct(obj); + gc->free_object(obj); + return nil; +} + +Method class_getInstanceMethod(Class aClass, SEL aSelector) +{ + CHECK_ARG(aClass); + CHECK_ARG(aSelector); + // If the class has a dtable installed, then we can use the fast path + if (classHasInstalledDtable(aClass)) + { + // Do a dtable lookup to find out which class the method comes from. + struct objc_slot *slot = objc_get_slot(aClass, aSelector); + if (NULL == slot) + { + slot = objc_get_slot(aClass, sel_registerName(sel_getName(aSelector))); + if (NULL == slot) + { + return NULL; + } + } + + // Now find the typed variant of the selector, with the correct types. + aSelector = slot->selector; + + // Then do the slow lookup to find the method. + return class_getInstanceMethodNonrecursive(slot->owner, aSelector); + } + Method m = class_getInstanceMethodNonrecursive(aClass, aSelector); + if (NULL != m) + { + return m; + } + return class_getInstanceMethod(class_getSuperclass(aClass), aSelector); +} + +Method class_getClassMethod(Class aClass, SEL aSelector) +{ + return class_getInstanceMethod(object_getClass((id)aClass), aSelector); +} + +Ivar class_getClassVariable(Class cls, const char* name) +{ + // Note: We don't have compiler support for cvars in ObjC + return class_getInstanceVariable(object_getClass((id)cls), name); +} + +size_t class_getInstanceSize(Class cls) +{ + if (Nil == cls) { return 0; } + return cls->instance_size; +} + +Ivar class_getInstanceVariable(Class cls, const char *name) +{ + if (name != NULL) + { + while (cls != Nil) + { + struct objc_ivar_list *ivarlist = cls->ivars; + + if (ivarlist != NULL) + { + for (int i = 0; i < ivarlist->count; i++) + { + Ivar ivar = &ivarlist->ivar_list[i]; + if (strcmp(ivar->name, name) == 0) + { + return ivar; + } + } + } + cls = class_getSuperclass(cls); + } + } + return NULL; +} + +// The format of the char* is undocumented. This function is only ever used in +// conjunction with class_setIvarLayout(). +const char *class_getIvarLayout(Class cls) +{ + CHECK_ARG(cls); + return (char*)cls->ivars; +} + + +const char * class_getName(Class cls) +{ + if (Nil == cls) { return "nil"; } + return cls->name; +} + +int class_getVersion(Class theClass) +{ + CHECK_ARG(theClass); + return theClass->version; +} + +const char *class_getWeakIvarLayout(Class cls) +{ + assert(0 && "Weak ivars not supported"); + return NULL; +} + +BOOL class_isMetaClass(Class cls) +{ + CHECK_ARG(cls); + return objc_test_class_flag(cls, objc_class_flag_meta); +} + +IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) +{ + if (Nil == cls) { return (IMP)0; } + SEL sel = sel_registerTypedName_np(sel_getName(name), types); + Method method = class_getInstanceMethodNonrecursive(cls, sel); + if (method == NULL) + { + class_addMethod(cls, sel, imp, types); + return NULL; + } + IMP old = (IMP)method->imp; + method->imp = imp; + + if (objc_test_class_flag(cls, objc_class_flag_resolved)) + { + objc_update_dtable_for_class(cls); + } + + return old; +} + + +void class_setIvarLayout(Class cls, const char *layout) +{ + if ((Nil == cls) || (NULL == layout)) { return; } + struct objc_ivar_list *list = (struct objc_ivar_list*)layout; + size_t listsize = sizeof(struct objc_ivar_list) + + sizeof(struct objc_ivar) * (list->count); + cls->ivars = malloc(listsize); + memcpy(cls->ivars, list, listsize); +} + +__attribute__((deprecated)) +Class class_setSuperclass(Class cls, Class newSuper) +{ + CHECK_ARG(cls); + CHECK_ARG(newSuper); + if (Nil == cls) { return Nil; } + Class oldSuper = cls->super_class; + cls->super_class = newSuper; + return oldSuper; +} + +void class_setVersion(Class theClass, int version) +{ + if (Nil == theClass) { return; } + theClass->version = version; +} + +void class_setWeakIvarLayout(Class cls, const char *layout) +{ + assert(0 && "Not implemented"); +} + +const char * ivar_getName(Ivar ivar) +{ + CHECK_ARG(ivar); + return ivar->name; +} + +ptrdiff_t ivar_getOffset(Ivar ivar) +{ + CHECK_ARG(ivar); + return ivar->offset; +} + +const char * ivar_getTypeEncoding(Ivar ivar) +{ + CHECK_ARG(ivar); + return ivar->type; +} + + +void method_exchangeImplementations(Method m1, Method m2) +{ + if (NULL == m1 || NULL == m2) { return; } + IMP tmp = (IMP)m1->imp; + m1->imp = m2->imp; + m2->imp = tmp; + objc_updateDtableForClassContainingMethod(m1); + objc_updateDtableForClassContainingMethod(m2); +} + +IMP method_getImplementation(Method method) +{ + if (NULL == method) { return (IMP)NULL; } + return (IMP)method->imp; +} + +SEL method_getName(Method method) +{ + if (NULL == method) { return (SEL)NULL; } + return (SEL)method->selector; +} + + +IMP method_setImplementation(Method method, IMP imp) +{ + if (NULL == method) { return (IMP)NULL; } + IMP old = (IMP)method->imp; + method->imp = old; + objc_updateDtableForClassContainingMethod(method); + return old; +} + +id objc_getRequiredClass(const char *name) +{ + CHECK_ARG(name); + id cls = objc_getClass(name); + if (nil == cls) + { + abort(); + } + return cls; +} + +static void freeMethodLists(Class aClass) +{ + struct objc_method_list *methods = aClass->methods; + while(methods != NULL) + { + for (int i=0 ; i<methods->count ; i++) + { + free((void*)methods->methods[i].types); + } + struct objc_method_list *current = methods; + methods = methods->next; + free(current); + } +} + +static void freeIvarLists(Class aClass) +{ + struct objc_ivar_list *ivarlist = aClass->ivars; + if (NULL == ivarlist) { return; } + + for (int i=0 ; i<ivarlist->count ; i++) + { + Ivar ivar = &ivarlist->ivar_list[i]; + free((void*)ivar->type); + free((void*)ivar->name); + } + free(ivarlist); +} + +/* + * Removes a class from the subclass list found on its super class. + * Must be called with the objc runtime mutex locked. + */ +static inline void safe_remove_from_subclass_list(Class cls) +{ + // If this class hasn't been added to the class hierarchy, then this is easy + if (!objc_test_class_flag(cls, objc_class_flag_resolved)) { return; } + Class sub = cls->super_class->subclass_list; + if (sub == cls) + { + cls->super_class->subclass_list = cls->sibling_class; + } + else + { + while (sub != NULL) + { + if (sub->sibling_class == cls) + { + sub->sibling_class = cls->sibling_class; + break; + } + sub = sub->sibling_class; + } + } +} + +void objc_disposeClassPair(Class cls) +{ + if (0 == cls) { return; } + Class meta = ((id)cls)->isa; + // Remove from the runtime system so nothing tries updating the dtable + // while we are freeing the class. + { + LOCK_RUNTIME_FOR_SCOPE(); + safe_remove_from_subclass_list(meta); + safe_remove_from_subclass_list(cls); + } + + // Free the method and ivar lists. + freeMethodLists(cls); + freeMethodLists(meta); + freeIvarLists(cls); + if (cls->dtable != uninstalled_dtable) + { + free_dtable(cls->dtable); + } + if (meta->dtable != uninstalled_dtable) + { + free_dtable(meta->dtable); + } + + // Free the class and metaclass + gc->free(meta); + gc->free(cls); +} + +Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) +{ + // Check the class doesn't already exist. + if (nil != objc_lookUpClass(name)) { return Nil; } + + Class newClass = gc->malloc(sizeof(struct objc_class) + extraBytes); + + if (Nil == newClass) { return Nil; } + + // Create the metaclass + Class metaClass = gc->malloc(sizeof(struct objc_class)); + + if (Nil == superclass) + { + /* + * Metaclasses of root classes are precious little flowers and work a + * little differently: + */ + metaClass->isa = metaClass; + metaClass->super_class = newClass; + } + else + { + // Initialize the metaclass + // Set the meta-metaclass pointer to the name. The runtime will fix this + // in objc_resolve_class(). + metaClass->isa = (Class)superclass->isa->isa->name; + metaClass->super_class = superclass->isa; + } + metaClass->name = strdup(name); + metaClass->info = objc_class_flag_meta | objc_class_flag_user_created | + objc_class_flag_new_abi; + metaClass->dtable = uninstalled_dtable; + metaClass->instance_size = sizeof(struct objc_class); + + // Set up the new class + newClass->isa = metaClass; + // Set the superclass pointer to the name. The runtime will fix this when + // the class links are resolved. + newClass->super_class = (Nil == superclass) ? Nil : (Class)(superclass->name); + + newClass->name = strdup(name); + newClass->info = objc_class_flag_class | objc_class_flag_user_created | + objc_class_flag_new_abi; + newClass->dtable = uninstalled_dtable; + + if (Nil == superclass) + { + newClass->instance_size = sizeof(struct objc_class); + } + else + { + newClass->instance_size = superclass->instance_size; + } + + return newClass; +} + + +void *object_getIndexedIvars(id obj) +{ + CHECK_ARG(obj); + size_t size = classForObject(obj)->instance_size; + if ((0 == size) && class_isMetaClass(classForObject(obj))) + { + Class cls = (Class)obj; + if (objc_test_class_flag(cls, objc_class_flag_new_abi)) + { + size = sizeof(struct objc_class); + } + else + { + size = sizeof(struct legacy_abi_objc_class); + } + } + return ((char*)obj) + size; +} + +Class object_getClass(id obj) +{ + CHECK_ARG(obj); + Class isa = classForObject(obj); + while ((Nil != isa) && objc_test_class_flag(isa, objc_class_flag_hidden_class)) + { + isa = isa->super_class; + } + return isa; +} + +Class object_setClass(id obj, Class cls) +{ + CHECK_ARG(obj); + // If this is a small object, then don't set its class. + uintptr_t addr = (uintptr_t)obj; + if (addr & 1) { return classForObject(obj); } + Class oldClass = obj->isa; + obj->isa = cls; + return oldClass; +} + +const char *object_getClassName(id obj) +{ + CHECK_ARG(obj); + return class_getName(object_getClass(obj)); +} + +void objc_registerClassPair(Class cls) +{ + LOCK_RUNTIME_FOR_SCOPE(); + class_table_insert(cls); +} + diff --git a/third_party/libobjc/sarray2.c b/third_party/libobjc/sarray2.c new file mode 100644 index 0000000000000000000000000000000000000000..b9ca967fa6914a186272edc8a2855c8cb21f17f3 --- /dev/null +++ b/third_party/libobjc/sarray2.c @@ -0,0 +1,204 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> + +#include "sarray2.h" +#include "visibility.h" + +static void *EmptyArrayData[256]; +static SparseArray EmptyArray = { 0xff, 0, 0, (void**)&EmptyArrayData}; + +#define MAX_INDEX(sarray) (sarray->mask >> sarray->shift) +#define DATA_SIZE(sarray) ((sarray->mask >> sarray->shift) + 1) + +// Tweak this value to trade speed for memory usage. Bigger values use more +// memory, but give faster lookups. +#define base_shift 8 +#define base_mask ((1<<base_shift) - 1) + +static void init_pointers(SparseArray * sarray) +{ + sarray->data = calloc(DATA_SIZE(sarray), sizeof(void*)); + if(sarray->shift != 0) + { + for(unsigned i=0 ; i<=MAX_INDEX(sarray) ; i++) + { + sarray->data[i] = &EmptyArray; + } + } +} +PRIVATE SparseArray * SparseArrayNewWithDepth(uint32_t depth) +{ + SparseArray * sarray = calloc(1, sizeof(SparseArray)); + sarray->refCount = 1; + sarray->shift = depth-base_shift; + sarray->mask = base_mask << sarray->shift; + init_pointers(sarray); + return sarray; +} + +PRIVATE SparseArray *SparseArrayNew() +{ + return SparseArrayNewWithDepth(32); +} +PRIVATE SparseArray *SparseArrayExpandingArray(SparseArray *sarray) +{ + // Expanding a child sarray has undefined results. + assert(sarray->refCount == 1); + SparseArray *new = calloc(1, sizeof(SparseArray)); + new->refCount = 1; + new->shift = sarray->shift; + new->mask = sarray->mask; + void **newData = malloc(DATA_SIZE(sarray) * sizeof(void*)); + for(unsigned i=0 ; i<=MAX_INDEX(sarray) ; i++) + { + newData[i] = &EmptyArray; + } + new->data = sarray->data; + // new is now an exact copy of sarray. + newData[0] = new; + sarray->data = newData; + // Now, any lookup in sarray for any value less than its capacity will have + // all non-zero values shifted away, resulting in 0. All lookups will + // therefore go to the new sarray. + sarray->shift += base_shift; + // Finally, set the mask to the correct value. Now all lookups should work. + sarray->mask <<= base_shift; + return new; +} + +static void *SparseArrayFind(SparseArray * sarray, uint32_t * index) +{ + uint32_t j = MASK_INDEX((*index)); + uint32_t max = MAX_INDEX(sarray); + if (sarray->shift == 0) + { + while (j<=max) + { + if (sarray->data[j] != SARRAY_EMPTY) + { + return sarray->data[j]; + } + (*index)++; + j++; + } + } + else while (j<max) + { + uint32_t zeromask = ~(sarray->mask >> base_shift); + while (j<max) + { + //Look in child nodes + if (sarray->data[j] != SARRAY_EMPTY) + { + void * ret = SparseArrayFind(sarray->data[j], index); + if (ret != SARRAY_EMPTY) + { + return ret; + } + // The recursive call will set index to the correct value for + // the next index, but won't update j + } + else + { + //Add 2^n to index so j is still correct + (*index) += 1<<sarray->shift; + //Zero off the next component of the index so we don't miss any. + *index &= zeromask; + } + //Go to the next child + j++; + } + } + return SARRAY_EMPTY; +} + +PRIVATE void *SparseArrayNext(SparseArray * sarray, uint32_t * idx) +{ + (*idx)++; + return SparseArrayFind(sarray, idx); +} + +PRIVATE void SparseArrayInsert(SparseArray * sarray, uint32_t index, void *value) +{ + if (sarray->shift > 0) + { + uint32_t i = MASK_INDEX(index); + SparseArray *child = sarray->data[i]; + if(&EmptyArray == child) + { + // Insert missing nodes + SparseArray * newsarray = calloc(1, sizeof(SparseArray)); + newsarray->refCount = 1; + if (base_shift >= sarray->shift) + { + newsarray->shift = 0; + } + else + { + newsarray->shift = sarray->shift - base_shift; + } + newsarray->mask = sarray->mask >> base_shift; + init_pointers(newsarray); + sarray->data[i] = newsarray; + child = newsarray; + } + else if (child->refCount > 1) + { + // Copy the copy-on-write part of the tree + sarray->data[i] = SparseArrayCopy(child); + SparseArrayDestroy(child); + child = sarray->data[i]; + } + SparseArrayInsert(child, index, value); + } + else + { + sarray->data[index & sarray->mask] = value; + } +} + +PRIVATE SparseArray *SparseArrayCopy(SparseArray * sarray) +{ + SparseArray *copy = calloc(1, sizeof(SparseArray)); + copy->refCount = 1; + copy->shift = sarray->shift; + copy->mask = sarray->mask; + copy->data = malloc(sizeof(void*) * DATA_SIZE(sarray)); + memcpy(copy->data, sarray->data, sizeof(void*) * DATA_SIZE(sarray)); + // If the sarray has children, increase their refcounts and link them + if (sarray->shift > 0) + { + for (unsigned int i = 0 ; i<=MAX_INDEX(sarray); i++) + { + SparseArray *child = copy->data[i]; + __sync_fetch_and_add(&child->refCount, 1); + // Non-lazy copy. Uncomment if debugging + // copy->data[i] = SparseArrayCopy(copy->data[i]); + } + } + return copy; +} + +PRIVATE void SparseArrayDestroy(SparseArray * sarray) +{ + // Don't really delete this sarray if its ref count is > 0 + if (sarray == &EmptyArray || + (__sync_sub_and_fetch(&sarray->refCount, 1) > 0)) + { + return; + } + + if(sarray->shift > 0) + { + uint32_t max = (sarray->mask >> sarray->shift) + 1; + for(uint32_t i=0 ; i<max ; i++) + { + SparseArrayDestroy((SparseArray*)sarray->data[i]); + } + } + free(sarray->data); + free(sarray); +} + diff --git a/third_party/libobjc/sarray2.h b/third_party/libobjc/sarray2.h new file mode 100644 index 0000000000000000000000000000000000000000..ce1d41a850469233e6a9feee0fd4f299d0d91851 --- /dev/null +++ b/third_party/libobjc/sarray2.h @@ -0,0 +1,139 @@ +/** + * Sparse Array + * + * Author: David Chisnall + * + * License: See COPYING.MIT + * + */ + +#ifndef _SARRAY_H_INCLUDED_ +#define _SARRAY_H_INCLUDED_ +#include <stdint.h> +#include <stdlib.h> +#include "visibility.h" + +/** + * Sparse arrays, used to implement dispatch tables. Current implementation is + * quite RAM-intensive and could be optimised. Maps 32-bit integers to pointers. + * + * Note that deletion from the array is not supported. This allows accesses to + * be done without locking; the worst that can happen is that the caller gets + * an old value (and if this is important to you then you should be doing your + * own locking). For this reason, you should be very careful when deleting a + * sparse array that there are no references to it held by other threads. + */ +typedef struct +{ + /** + * Mask value applied to the index when generating an index in this + * sub-array. + */ + uint32_t mask; + /** + * Number of bits that the masked value should be right shifted by to get + * the index in the subarray. If this value is greater than zero, then the + * value in the array is another SparseArray*. + */ + uint32_t shift; + /** + * The reference count for this. Used for copy-on-write. When making a + * copy of a sparse array, we only copy the root node, and increment the + * reference count of the remaining nodes. When modifying any leaf node, + * we copy if its reference count is greater than one. + */ + uint32_t refCount; + /** + * The data stored in this sparse array node. + */ + void ** data; +} SparseArray; + +/** + * Turn an index in the array into an index in the current depth. + */ +#define MASK_INDEX(index) \ + ((index & sarray->mask) >> sarray->shift) + +#define SARRAY_EMPTY ((void*)0) +/** + * Look up the specified value in the sparse array. This is used in message + * dispatch and so has been put in the header to allow compilers to inline it, + * even though this breaks the abstraction. + */ +static inline void* SparseArrayLookup(SparseArray * sarray, uint32_t index) +{ + // This unrolled version of the commented-out segment below only works with + // sarrays that use one-byte leaves. It's really ugly, but seems to be faster. + // With this version, we get the same performance as the old GNU code, but + // with about half the memory usage. + uint32_t i = index; + switch (sarray->shift) + { + default: UNREACHABLE("broken sarray"); + case 0: + return sarray->data[i & 0xff]; + case 8: + return + ((SparseArray*)sarray->data[(i & 0xff00)>>8])->data[(i & 0xff)]; + case 16: + return + ((SparseArray*)((SparseArray*) + sarray->data[(i & 0xff0000)>>16])-> + data[(i & 0xff00)>>8])->data[(i & 0xff)]; + case 24: + return + ((SparseArray*)((SparseArray*)((SparseArray*) + sarray->data[(i & 0xff000000)>>24])-> + data[(i & 0xff0000)>>16])-> + data[(i & 0xff00)>>8])->data[(i & 0xff)]; + } + /* + while(sarray->shift > 0) + { + uint32_t i = MASK_INDEX(index); + sarray = (SparseArray*) sarray->data[i]; + } + uint32_t i = index & sarray->mask; + return sarray->data[i]; + */ +} +/** + * Create a new sparse array. + */ +SparseArray *SparseArrayNew(); +/** + * Creates a new sparse array with the specified capacity. The depth indicates + * the number of bits to use for the key. Must be a value between 8 and 32 and + * should ideally be a multiple of base_shift. + */ +SparseArray *SparseArrayNewWithDepth(uint32_t depth); +/** + * Returns a new sparse array created by adding this one as the first child + * node in an expanded one. + */ +SparseArray *SparseArrayExpandingArray(SparseArray *sarray); +/** + * Insert a value at the specified index. + */ +void SparseArrayInsert(SparseArray * sarray, uint32_t index, void * value); +/** + * Destroy the sparse array. Note that calling this while other threads are + * performing lookups is guaranteed to break. + */ +void SparseArrayDestroy(SparseArray * sarray); +/** + * Iterate through the array. Returns the next non-NULL value after index and + * sets index to the following value. For example, an array containing values + * at 0 and 10 will, if called with index set to 0 first return the value at 0 + * and set index to 1. A subsequent call with index set to 1 will return the + * value at 10 and set index to 11. + */ +void * SparseArrayNext(SparseArray * sarray, uint32_t * index); + +/** + * Creates a copy of the sparse array. + */ +SparseArray *SparseArrayCopy(SparseArray * sarray); + +#endif //_SARRAY_H_INCLUDED_ diff --git a/third_party/libobjc/selector.h b/third_party/libobjc/selector.h new file mode 100644 index 0000000000000000000000000000000000000000..116d0b9bdfea6a5a2c0b35fa7ac513c58363cdc5 --- /dev/null +++ b/third_party/libobjc/selector.h @@ -0,0 +1,82 @@ +#ifndef OBJC_SELECTOR_H_INCLUDED +#define OBJC_SELECTOR_H_INCLUDED +/** + * Structure used to store the types for a selector. This allows for a quick + * test to see whether a selector is polymorphic and allows enumeration of all + * type encodings for a given selector. + * + * This is the same size as an objc_selector, so we can allocate them from the + * objc_selector pool. + * + * Note: For ABI v10, we can probably do something a bit more sensible here and + * make selectors into a linked list. + */ +struct sel_type_list +{ + const char *value; + struct sel_type_list *next; +}; + +/** + * Structure used to store selectors in the list. + */ +struct objc_selector +{ + union + { + /** + * The name of this selector. Used for unregistered selectors. + */ + const char *name; + /** + * The index of this selector in the selector table. When a selector + * is registered with the runtime, its name is replaced by an index + * uniquely identifying this selector. The index is used for dispatch. + */ + uintptr_t index; + }; + /** + * The Objective-C type encoding of the message identified by this selector. + */ + const char * types; +}; + +/** + * Returns the untyped variant of a selector. + */ +__attribute__((unused)) +static uint32_t get_untyped_idx(SEL aSel) +{ + SEL untyped = sel_registerTypedName_np(sel_getName(aSel), 0); + return untyped->index; +} + +__attribute__((unused)) +static SEL sel_getUntyped(SEL aSel) +{ + return sel_registerTypedName_np(sel_getName(aSel), 0); +} + +/** + * Returns whether a selector is mapped. + */ +BOOL isSelRegistered(SEL sel); + +/** + * Registers the selector. This selector may be returned later, so it must not + * be freed. + */ +SEL objc_register_selector(SEL aSel); + +/** + * SELECTOR() macro to work around the fact that GCC hard-codes the type of + * selectors. This is functionally equivalent to @selector(), but it ensures + * that the selector has the type that the runtime uses for selectors. + */ +#ifdef __clang__ +#define SELECTOR(x) @selector(x) +#else +#define SELECTOR(x) (SEL)@selector(x) +#endif + +#endif // OBJC_SELECTOR_H_INCLUDED diff --git a/third_party/libobjc/selector_table.c b/third_party/libobjc/selector_table.c new file mode 100644 index 0000000000000000000000000000000000000000..d75e1c494c0c3667d36eabf682d843f88b46909e --- /dev/null +++ b/third_party/libobjc/selector_table.c @@ -0,0 +1,632 @@ +/** + * Handle selector uniquing. + * + * When building, you may define TYPE_DEPENDENT_DISPATCH to enable message + * sends to depend on their types. + */ +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <ctype.h> +#include "lock.h" +#include "sarray2.h" +#include "objc/runtime.h" +#include "method_list.h" +#include "class.h" +#include "selector.h" +#include "visibility.h" + +#ifdef TYPE_DEPENDENT_DISPATCH +# define TDD(x) x +#else +# define TDD(x) +#endif + +#define fprintf(...) + + +// Define the pool allocator for selectors. This is a simple bump-the-pointer +// allocator for low-overhead allocation. +#define POOL_NAME selector +#define POOL_TYPE struct objc_selector +#include "pool.h" + + +/** + * The number of selectors currently registered. When a selector is + * registered, its name field is replaced with its index in the selector_list + * array. + */ +static uint32_t selector_count = 1; +/** + * Mapping from selector numbers to selector names. + */ +PRIVATE SparseArray *selector_list = NULL; + +// Get the functions for string hashing +#include "string_hash.h" + +PRIVATE inline BOOL isSelRegistered(SEL sel) +{ + if ((uintptr_t)sel->name < (uintptr_t)selector_count) + { + return YES; + } + return NO; +} + +static const char *sel_getNameNonUnique(SEL sel) +{ + const char *name = sel->name; + if (isSelRegistered(sel)) + { + struct sel_type_list * list = + SparseArrayLookup(selector_list, sel->index); + name = (list == NULL) ? NULL : list->value; + } + if (NULL == name) + { + name = ""; + } + return name; +} + +/** + * Skip anything in a type encoding that is irrelevant to the comparison + * between selectors, including type qualifiers and argframe info. + */ +static const char *skip_irrelevant_type_info(const char *t) +{ + switch (*t) + { + default: return t; + case 'r': case 'n': case 'N': case 'o': case 'O': case 'R': + case 'V': case '!': case '0'...'9': + return skip_irrelevant_type_info(t+1); + } +} + +static BOOL selector_types_equal(const char *t1, const char *t2) +{ + if (t1 == NULL || t2 == NULL) { return t1 == t2; } + + while (('\0' != *t1) && ('\0' != *t1)) + { + t1 = skip_irrelevant_type_info(t1); + t2 = skip_irrelevant_type_info(t2); + // This is a really ugly hack. For some stupid reason, the people + // designing Objective-C type encodings decided to allow * as a + // shorthand for char*, because strings are 'special'. Unfortunately, + // FSF GCC generates "*" for @encode(BOOL*), while Clang and Apple GCC + // generate "^c" or "^C" (depending on whether BOOL is declared + // unsigned). + // + // The correct fix is to remove * completely from type encodings, but + // unfortunately my time machine is broken so I can't travel to 1986 + // and apply a cluebat to those responsible. + if ((*t1 == '*') && (*t2 != '*')) + { + if (*t2 == '^' && (((*(t2+1) == 'C') || (*(t2+2) == 'c')))) + { + t2++; + } + else + { + return NO; + } + } + else if ((*t2 == '*') && (*t1 != '*')) + { + if (*t1 == '^' && (((*(t1+1) == 'C') || (*(t1+1) == 'c')))) + { + t1++; + } + else + { + return NO; + } + } + else if (*t1 != *t2) + { + return NO; + } + + if ('\0' != *t1) { t1++; } + if ('\0' != *t2) { t2++; } + } + return YES; +} + +#ifdef TYPE_DEPENDENT_DISPATCH + +static BOOL selector_types_equivalent(const char *t1, const char *t2) +{ + // We always treat untyped selectors as having the same type as typed + // selectors, for dispatch purposes. + if (t1 == NULL || t2 == NULL) { return YES; } + + return selector_types_equal(t1, t2); +} +#endif + +/** + * Compare whether two selectors are identical. + */ +static int selector_identical(const void *k, + const SEL value) +{ + SEL key = (SEL)k; + fprintf(stderr, "Comparing %s %s, %s %s\n", sel_getNameNonUnique(key), sel_getNameNonUnique(value), sel_getType_np(key), sel_getType_np(value)); + return string_compare(sel_getNameNonUnique(key), sel_getNameNonUnique(value)) && + selector_types_equal(sel_getType_np(key), sel_getType_np(value)); +} + +/** + * Compare selectors based on whether they are treated as equivalent for the + * purpose of dispatch. + */ +static int selector_equal(const void *k, + const SEL value) +{ +#ifdef TYPE_DEPENDENT_DISPATCH + return selector_identical(k, value); +#else + SEL key = (SEL)k; + return string_compare(sel_getNameNonUnique(key), sel_getNameNonUnique(value)); +#endif +} + +/** + * Hash a selector. + */ +static inline uint32_t hash_selector(const void *s) +{ + SEL sel = (SEL)s; + uint32_t hash = 5381; + const char *str = sel_getNameNonUnique(sel); + uint32_t c; + while((c = (uint32_t)*str++)) + { + hash = hash * 33 + c; + } +#ifdef TYPE_DEPENDENT_DISPATCH + // We can't use all of the values in the type encoding for the hash, + // because our equality test is a bit more complex than simple string + // encoding (for example, * and ^C have to be considered equivalent, since + // they are both used as encodings for C strings in different situations) + if ((str = sel_getType_np(sel))) + { + while((c = (uint32_t)*str++)) + { + switch (c) + { + case '@': case 'i': case 'I': case 'l': case 'L': + case 'q': case 'Q': case 's': case 'S': + hash = hash * 33 + c; + } + } + } +#endif + return hash; +} + +#define MAP_TABLE_NAME selector +#define MAP_TABLE_COMPARE_FUNCTION selector_identical +#define MAP_TABLE_HASH_KEY hash_selector +#define MAP_TABLE_HASH_VALUE hash_selector +#include "hash_table.h" +/** + * Table of registered selector. Maps from selector to selector. + */ +static selector_table *sel_table; + +/** + * Lock protecting the selector table. + */ +mutex_t selector_table_lock; + + +/** + * Resizes the dtables to ensure that they can store as many selectors as + * exist. + */ +void objc_resize_dtables(uint32_t); + +/** + * Create data structures to store selectors. + */ +PRIVATE void init_selector_tables() +{ + selector_list = SparseArrayNew(); + INIT_LOCK(selector_table_lock); + selector_initialize(&sel_table, 4096); +} + +static SEL selector_lookup(const char *name, const char *types) +{ + struct objc_selector sel = {{name}, types}; + return selector_table_get(sel_table, &sel); +} +static inline void add_selector_to_table(SEL aSel, int32_t uid, uint32_t idx) +{ + //fprintf(stderr, "Sel %s uid: %d, idx: %d, hash: %d\n", sel_getNameNonUnique(aSel), uid, idx, hash_selector(aSel)); + struct sel_type_list *typeList = + (struct sel_type_list *)selector_pool_alloc(); + typeList->value = aSel->name; + typeList->next = 0; + // Store the name. + SparseArrayInsert(selector_list, idx, typeList); + // Store the selector. + selector_insert(sel_table, aSel); + // Set the selector's name to the uid. + aSel->name = (const char*)(uintptr_t)uid; +} +/** + * Really registers a selector. Must be called with the selector table locked. + */ +static inline void register_selector_locked(SEL aSel) +{ + uintptr_t idx = selector_count++; + if (NULL == aSel->types) + { + fprintf(stderr, "Registering selector %d %s\n", (int)idx, sel_getNameNonUnique(aSel)); + add_selector_to_table(aSel, idx, idx); + objc_resize_dtables(selector_count); + return; + } + SEL untyped = selector_lookup(aSel->name, 0); + // If this has a type encoding, store the untyped version too. + if (untyped == NULL) + { + untyped = selector_pool_alloc(); + untyped->name = aSel->name; + untyped->types = 0; + fprintf(stderr, "Registering selector %d %s\n", (int)idx, sel_getNameNonUnique(aSel)); + add_selector_to_table(untyped, idx, idx); + // If we are in type dependent dispatch mode, the uid for the typed + // and untyped versions will be different + idx++; selector_count++; + } + else + { + // Make sure we only store one name + aSel->name = sel_getNameNonUnique(untyped); + } + uintptr_t uid = (uintptr_t)untyped->name; + TDD(uid = idx); + fprintf(stderr, "Registering typed selector %d %s %s\n", (int)uid, sel_getNameNonUnique(aSel), sel_getType_np(aSel)); + add_selector_to_table(aSel, uid, idx); + + // Add this set of types to the list. + // This is quite horrible. Most selectors will only have one type + // encoding, so we're wasting a lot of memory like this. + struct sel_type_list *typeListHead = + SparseArrayLookup(selector_list, untyped->index); + struct sel_type_list *typeList = + (struct sel_type_list *)selector_pool_alloc(); + typeList->value = aSel->types; + typeList->next = typeListHead->next; + typeListHead->next = typeList; + objc_resize_dtables(selector_count); +} +/** + * Registers a selector. This assumes that the argument is never deallocated. + */ +PRIVATE SEL objc_register_selector(SEL aSel) +{ + if (isSelRegistered(aSel)) + { + return aSel; + } + // Check that this isn't already registered, before we try + SEL registered = selector_lookup(aSel->name, aSel->types); + if (NULL != registered && selector_equal(aSel, registered)) + { + aSel->name = registered->name; + return registered; + } + LOCK(&selector_table_lock); + register_selector_locked(aSel); + UNLOCK(&selector_table_lock); + return aSel; +} + +/** + * Registers a selector by copying the argument. + */ +static SEL objc_register_selector_copy(SEL aSel, BOOL copyArgs) +{ + // If an identical selector is already registered, return it. + SEL copy = selector_lookup(aSel->name, aSel->types); + //fprintf(stderr, "Checking if old selector is registered: %d (%d)\n", NULL != copy ? selector_equal(aSel, copy) : 0, ((NULL != copy) && selector_equal(aSel, copy))); + if ((NULL != copy) && selector_identical(aSel, copy)) + { + //fprintf(stderr, "Not adding new copy\n"); + return copy; + } + LOCK_FOR_SCOPE(&selector_table_lock); + copy = selector_lookup(aSel->name, aSel->types); + if (NULL != copy && selector_identical(aSel, copy)) + { + return copy; + } + // Create a copy of this selector. + copy = selector_pool_alloc(); + copy->name = copyArgs ? strdup(aSel->name) : aSel->name; + copy->types = (NULL == aSel->types) ? NULL : + (copyArgs ? strdup(aSel->types) : aSel->types); + // Try to register the copy as the authoritative version + register_selector_locked(copy); + return copy; +} + +PRIVATE uint32_t sel_nextTypeIndex(uint32_t untypedIdx, uint32_t idx) +{ + struct sel_type_list *list = + SparseArrayLookup(selector_list, untypedIdx); + + if (NULL == list) { return 0; } + + const char *selName = list->value; + list = list->next; + BOOL found = untypedIdx == idx; + while (NULL != list) + { + SEL sel = selector_lookup(selName, list->value); + if (sel->index == untypedIdx) { return 0; } + if (found) + { + return sel->index; + } + found = (sel->index == idx); + } + return 0; +} + +/** + * Public API functions. + */ + +const char *sel_getName(SEL sel) +{ + if (NULL == sel) { return "<null selector>"; } + const char *name = sel->name; + if (isSelRegistered(sel)) + { + struct sel_type_list * list = + SparseArrayLookup(selector_list, sel->index); + name = (list == NULL) ? NULL : list->value; + } + else + { + SEL old = selector_lookup(sel->name, sel->types); + if (NULL != old) + { + return sel_getName(old); + } + } + if (NULL == name) + { + name = ""; + } + return name; +} + +SEL sel_getUid(const char *selName) +{ + return sel_registerName(selName); +} + +BOOL sel_isEqual(SEL sel1, SEL sel2) +{ + if ((0 == sel1) || (0 == sel2)) + { + return sel1 == sel2; + } + if (sel1->name == sel2->name) + { + return YES; + } + // Otherwise, do a slow compare + return string_compare(sel_getNameNonUnique(sel1), sel_getNameNonUnique(sel2)) TDD(&& + (sel1->types == NULL || sel2->types == NULL || + selector_types_equivalent(sel_getType_np(sel1), sel_getType_np(sel2)))); +} + +SEL sel_registerName(const char *selName) +{ + if (NULL == selName) { return NULL; } + struct objc_selector sel = {{selName}, 0}; + return objc_register_selector_copy(&sel, YES); +} + +SEL sel_registerTypedName_np(const char *selName, const char *types) +{ + if (NULL == selName) { return NULL; } + struct objc_selector sel = {{selName}, types}; + return objc_register_selector_copy(&sel, YES); +} + +const char *sel_getType_np(SEL aSel) +{ + if (NULL == aSel) { return NULL; } + return aSel->types; +} + + +unsigned sel_copyTypes_np(const char *selName, const char **types, unsigned count) +{ + if (NULL == selName) { return 0; } + SEL untyped = selector_lookup(selName, 0); + if (untyped == NULL) { return 0; } + + struct sel_type_list *l = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)untyped->name); + // Skip the head, which just contains the name, not the types. + l = l->next; + + if (count == 0) + { + while (NULL != l) + { + count++; + l = l->next; + } + return count; + } + + unsigned found = 0; + while (NULL != l) + { + if (found<count) + { + types[found] = l->value; + } + found++; + l = l->next; + } + return found; +} + +unsigned sel_copyTypedSelectors_np(const char *selName, SEL *const sels, unsigned count) +{ + if (NULL == selName) { return 0; } + SEL untyped = selector_lookup(selName, 0); + if (untyped == NULL) { return 0; } + + struct sel_type_list *l = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)untyped->name); + // Skip the head, which just contains the name, not the types. + l = l->next; + + if (count == 0) + { + while (NULL != l) + { + count++; + l = l->next; + } + return count; + } + + unsigned found = 0; + while (NULL != l && found<count) + { + sels[found++] = selector_lookup(selName, l->value); + l = l->next; + } + return found; +} + +PRIVATE void objc_register_selectors_from_list(struct objc_method_list *l) +{ + for (int i=0 ; i<l->count ; i++) + { + Method m = &l->methods[i]; + struct objc_selector sel = { {(const char*)m->selector}, m->types }; + m->selector = objc_register_selector_copy(&sel, NO); + } +} +/** + * Register all of the (unregistered) selectors that are used in a class. + */ +PRIVATE void objc_register_selectors_from_class(Class class) +{ + for (struct objc_method_list *l=class->methods ; NULL!=l ; l=l->next) + { + objc_register_selectors_from_list(l); + } +} +PRIVATE void objc_register_selector_array(SEL selectors, unsigned long count) +{ + // GCC is broken and always sets the count to 0, so we ignore count until + // we can throw stupid and buggy compilers in the bin. + for (unsigned long i=0 ; (NULL != selectors[i].name) ; i++) + { + objc_register_selector(&selectors[i]); + } +} + + +/** + * Legacy GNU runtime compatibility. + * + * All of the functions in this section are deprecated and should not be used + * in new code. + */ +#ifndef NO_LEGACY +SEL sel_get_typed_uid (const char *name, const char *types) +{ + if (NULL == name) { return NULL; } + SEL sel = selector_lookup(name, types); + if (NULL == sel) { return sel_registerTypedName_np(name, types); } + + struct sel_type_list *l = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)sel->name); + // Skip the head, which just contains the name, not the types. + l = l->next; + if (NULL != l) + { + sel = selector_lookup(name, l->value); + } + return sel; +} + +SEL sel_get_any_typed_uid (const char *name) +{ + if (NULL == name) { return NULL; } + SEL sel = selector_lookup(name, 0); + if (NULL == sel) { return sel_registerName(name); } + + struct sel_type_list *l = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)sel->name); + // Skip the head, which just contains the name, not the types. + l = l->next; + if (NULL != l) + { + sel = selector_lookup(name, l->value); + } + return sel; +} + +SEL sel_get_any_uid (const char *name) +{ + return selector_lookup(name, 0); +} + +SEL sel_get_uid(const char *name) +{ + return selector_lookup(name, 0); +} + +const char *sel_get_name(SEL selector) +{ + return sel_getNameNonUnique(selector); +} + +BOOL sel_is_mapped(SEL selector) +{ + return isSelRegistered(selector); +} + +const char *sel_get_type(SEL selector) +{ + return sel_getType_np(selector); +} + +SEL sel_register_name(const char *name) +{ + return sel_registerName(name); +} + +SEL sel_register_typed_name(const char *name, const char *type) +{ + return sel_registerTypedName_np(name, type); +} + +BOOL sel_eq(SEL s1, SEL s2) +{ + return sel_isEqual(s1, s2); +} + +#endif // NO_LEGACY diff --git a/third_party/libobjc/sendmsg2.c b/third_party/libobjc/sendmsg2.c new file mode 100644 index 0000000000000000000000000000000000000000..0b026a8225d272c24c8b009fdb165ababb5302e0 --- /dev/null +++ b/third_party/libobjc/sendmsg2.c @@ -0,0 +1,409 @@ +#include "objc/runtime.h" +#include "lock.h" +#include "dtable.h" +#include "selector.h" +#include "loader.h" +#include "objc/hooks.h" +#include <stdint.h> +#include <stdio.h> + +void objc_send_initialize(id object); + +static long long nil_method(id self, SEL _cmd) { return 0; } +static long double nil_method_D(id self, SEL _cmd) { return 0; } +static double nil_method_d(id self, SEL _cmd) { return 0; } +static float nil_method_f(id self, SEL _cmd) { return 0; } + +static struct objc_slot nil_slot = { Nil, Nil, 0, 1, (IMP)nil_method }; +static struct objc_slot nil_slot_D = { Nil, Nil, 0, 1, (IMP)nil_method_D }; +static struct objc_slot nil_slot_d = { Nil, Nil, 0, 1, (IMP)nil_method_d }; +static struct objc_slot nil_slot_f = { Nil, Nil, 0, 1, (IMP)nil_method_f }; + +typedef struct objc_slot *Slot_t; + +Slot_t objc_msg_lookup_sender(id *receiver, SEL selector, id sender); + +// Default implementations of the two new hooks. Return NULL. +static id objc_proxy_lookup_null(id receiver, SEL op) { return nil; } +static Slot_t objc_msg_forward3_null(id receiver, SEL op) { return &nil_slot; } + +id (*objc_proxy_lookup)(id receiver, SEL op) = objc_proxy_lookup_null; +Slot_t (*__objc_msg_forward3)(id receiver, SEL op) = objc_msg_forward3_null; + +#ifndef NO_SELECTOR_MISMATCH_WARNINGS +static struct objc_slot* objc_selector_type_mismatch(Class cls, SEL + selector, Slot_t result) +{ + fprintf(stderr, "Calling [%s %c%s] with incorrect signature. " + "Method has %s, selector has %s\n", + cls->name, + class_isMetaClass(cls) ? '+' : '-', + sel_getName(selector), + result->types, + sel_getType_np(selector)); + return result; +} +#else +static struct objc_slot* objc_selector_type_mismatch(Class cls, SEL + selector, Slot_t result) +{ + return result; +} +#endif + +struct objc_slot* (*_objc_selector_type_mismatch)(Class cls, SEL + selector, struct objc_slot *result) = objc_selector_type_mismatch; +static +// Uncomment for debugging +//__attribute__((noinline)) +__attribute__((always_inline)) +Slot_t objc_msg_lookup_internal(id *receiver, + SEL selector, + id sender) +{ +retry:; + Class class = classForObject((*receiver)); + Slot_t result = objc_dtable_lookup(class->dtable, selector->index); + if (UNLIKELY(0 == result)) + { + dtable_t dtable = dtable_for_class(class); + /* Install the dtable if it hasn't already been initialized. */ + if (dtable == uninstalled_dtable) + { + objc_send_initialize(*receiver); + dtable = dtable_for_class(class); + result = objc_dtable_lookup(dtable, selector->index); + } + else + { + // Check again incase another thread updated the dtable while we + // weren't looking + result = objc_dtable_lookup(dtable, selector->index); + } + if (0 == result) + { + if (!isSelRegistered(selector)) + { + objc_register_selector(selector); + // This should be a tail call, but GCC is stupid and won't let + // us tail call an always_inline function. + goto retry; + } + if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector)))) + { + return _objc_selector_type_mismatch(class, selector, result); + } + id newReceiver = objc_proxy_lookup(*receiver, selector); + // If some other library wants us to play forwarding games, try + // again with the new object. + if (nil != newReceiver) + { + *receiver = newReceiver; + return objc_msg_lookup_sender(receiver, selector, sender); + } + if (0 == result) + { + result = __objc_msg_forward3(*receiver, selector); + } + } + } + return result; +} + +PRIVATE +IMP slowMsgLookup(id *receiver, SEL cmd) +{ + return objc_msg_lookup_sender(receiver, cmd, nil)->method; +} + +PRIVATE void logInt(void *a) +{ + fprintf(stderr, "Value: %p\n", a); +} + +Slot_t (*objc_plane_lookup)(id *receiver, SEL op, id sender) = + objc_msg_lookup_internal; + +Slot_t objc_msg_lookup_sender_non_nil(id *receiver, SEL selector, id sender) +{ + return objc_msg_lookup_internal(receiver, selector, sender); +} + +/** + * New Objective-C lookup function. This permits the lookup to modify the + * receiver and also supports multi-dimensional dispatch based on the sender. + */ +Slot_t objc_msg_lookup_sender(id *receiver, SEL selector, id sender) +{ + // Returning a nil slot allows the caller to cache the lookup for nil too, + // although this is not particularly useful because the nil method can be + // inlined trivially. + if (UNLIKELY(*receiver == nil)) + { + // Return the correct kind of zero, depending on the type encoding. + if (selector->types) + { + const char *t = selector->types; + // Skip type qualifiers + while ('r' == *t || 'n' == *t || 'N' == *t || 'o' == *t || + 'O' == *t || 'R' == *t || 'V' == *t) + { + t++; + } + switch (selector->types[0]) + { + case 'D': return &nil_slot_D; + case 'd': return &nil_slot_d; + case 'f': return &nil_slot_f; + } + } + return &nil_slot; + } + + /* + * The self pointer is invalid in some code. This test is disabled until + * we can guarantee that it is not (e.g. with GCKit) + if (__builtin_expect(sender == nil + || + (sender->isa->info & (*receiver)->isa->info & _CLS_PLANE_AWARE),1)) + */ + { + return objc_msg_lookup_internal(receiver, selector, sender); + } + // If we are in plane-aware code + void *senderPlaneID = *((void**)sender - 1); + void *receiverPlaneID = *((void**)receiver - 1); + if (senderPlaneID == receiverPlaneID) + { + return objc_msg_lookup_internal(receiver, selector, sender); + } + return objc_plane_lookup(receiver, selector, sender); +} + +Slot_t objc_slot_lookup_super(struct objc_super *super, SEL selector) +{ + id receiver = super->receiver; + if (receiver) + { + Class class = super->class; + Slot_t result = objc_dtable_lookup(dtable_for_class(class), + selector->index); + if (0 == result) + { + Class class = classForObject(receiver); + // Dtable should always be installed in the superclass + // Unfortunately, some stupid code (PyObjC) decides to use this + // mechanism for everything + if (dtable_for_class(class) == uninstalled_dtable) + { + if (class_isMetaClass(class)) + { + objc_send_initialize(receiver); + } + else + { + objc_send_initialize((id)class); + } + objc_send_initialize((id)class); + return objc_slot_lookup_super(super, selector); + } + result = &nil_slot; + } + return result; + } + else + { + return &nil_slot; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Profiling +//////////////////////////////////////////////////////////////////////////////// + +/** + * Mutex used to protect non-thread-safe parts of the profiling subsystem. + */ +static mutex_t profileLock; +/** + * File used for writing the profiling symbol table. + */ +static FILE *profileSymbols; +/** + * File used for writing the profiling data. + */ +static FILE *profileData; + +struct profile_info +{ + const char *module; + int32_t callsite; + IMP method; +}; + +static void profile_init(void) +{ + INIT_LOCK(profileLock); + profileSymbols = fopen("objc_profile.symbols", "a"); + profileData = fopen("objc_profile.data", "a"); + // Write markers indicating a new run. + fprintf(profileSymbols, "=== NEW TRACE ===\n"); + struct profile_info profile_data = { 0, 0, 0 }; + fwrite(&profile_data, sizeof(profile_data), 1, profileData); +} + +void objc_profile_write_symbols(char **symbols) +{ + if (NULL == profileData) + { + LOCK_RUNTIME_FOR_SCOPE(); + if (NULL == profileData) + { + profile_init(); + } + } + LOCK(&profileLock); + while(*symbols) + { + char *address = *(symbols++); + char *symbol = *(symbols++); + fprintf(profileSymbols, "%zx %s\n", (size_t)address, symbol); + } + UNLOCK(&profileLock); + fflush(profileSymbols); +} + +/** + * Profiling version of the slot lookup. This takes a unique ID for the module + * and the callsite as extra arguments. The type of the receiver and the + * address of the resulting function are then logged to a file. These can then + * be used to determine whether adding slot caching is worthwhile, and whether + * any of the resulting methods should be speculatively inlined. + */ +void objc_msg_profile(id receiver, IMP method, + const char *module, int32_t callsite) +{ + // Initialize the logging lazily. This prevents us from wasting any memory + // when we are not profiling. + if (NULL == profileData) + { + LOCK_RUNTIME_FOR_SCOPE(); + if (NULL == profileData) + { + profile_init(); + } + } + struct profile_info profile_data = { module, callsite, method }; + fwrite(&profile_data, sizeof(profile_data), 1, profileData); +} + +/** + * Looks up a slot without invoking any forwarding mechanisms + */ +Slot_t objc_get_slot(Class cls, SEL selector) +{ + Slot_t result = objc_dtable_lookup(cls->dtable, selector->index); + if (0 == result) + { + void *dtable = dtable_for_class(cls); + /* Install the dtable if it hasn't already been initialized. */ + if (dtable == uninstalled_dtable) + { + dtable = dtable_for_class(cls); + result = objc_dtable_lookup(dtable, selector->index); + } + else + { + // Check again incase another thread updated the dtable while we + // weren't looking + result = objc_dtable_lookup(dtable, selector->index); + } + if (NULL == result) + { + if (!isSelRegistered(selector)) + { + objc_register_selector(selector); + return objc_get_slot(cls, selector); + } + if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector)))) + { + return _objc_selector_type_mismatch(cls, selector, result); + } + } + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Public API +//////////////////////////////////////////////////////////////////////////////// + +BOOL class_respondsToSelector(Class cls, SEL selector) +{ + if (0 == selector || 0 == cls) { return NO; } + + return NULL != objc_get_slot(cls, selector); +} + +IMP class_getMethodImplementation(Class cls, SEL name) +{ + if ((Nil == cls) || (NULL == name)) { return (IMP)0; } + Slot_t slot = objc_get_slot(cls, name); + return NULL != slot ? slot->method : __objc_msg_forward2(nil, name); +} + +IMP class_getMethodImplementation_stret(Class cls, SEL name) +{ + return class_getMethodImplementation(cls, name); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Legacy compatibility +//////////////////////////////////////////////////////////////////////////////// + +#ifndef NO_LEGACY +/** + * Legacy message lookup function. + */ +BOOL __objc_responds_to(id object, SEL sel) +{ + return class_respondsToSelector(classForObject(object), sel); +} + +IMP get_imp(Class cls, SEL selector) +{ + return class_getMethodImplementation(cls, selector); +} + +/** + * Message send function that only ever worked on a small subset of compiler / + * architecture combinations. + */ +void *objc_msg_sendv(void) +{ + fprintf(stderr, "objc_msg_sendv() never worked correctly. Don't use it.\n"); + abort(); +} +#endif +/** + * Legacy message lookup function. Does not support fast proxies or safe IMP + * caching. + */ +IMP objc_msg_lookup(id receiver, SEL selector) +{ + if (nil == receiver) { return (IMP)nil_method; } + + id self = receiver; + Slot_t slot = objc_msg_lookup_internal(&self, selector, nil); + if (self != receiver) + { + slot = __objc_msg_forward3(receiver, selector); + } + return slot->method; +} + +IMP objc_msg_lookup_super(struct objc_super *super, SEL selector) +{ + return objc_slot_lookup_super(super, selector)->method; +} diff --git a/third_party/libobjc/slot_pool.h b/third_party/libobjc/slot_pool.h new file mode 100644 index 0000000000000000000000000000000000000000..68502ebe81388f5e32fa5e54acd6cab364183920 --- /dev/null +++ b/third_party/libobjc/slot_pool.h @@ -0,0 +1,18 @@ +#define POOL_NAME slot +#define POOL_TYPE struct objc_slot +#include "pool.h" + +/** + * Allocates a new slot and initialises it for this method. + */ +static inline struct objc_slot *new_slot_for_method_in_class(Method method, + Class class) +{ + struct objc_slot *slot = slot_pool_alloc(); + slot->owner = class; + slot->types = method->selector->types; + slot->selector = method->selector; + slot->method = method->imp; + slot->version = 1; + return slot; +} diff --git a/third_party/libobjc/spinlock.h b/third_party/libobjc/spinlock.h new file mode 100644 index 0000000000000000000000000000000000000000..f3dc2cfb296dce29624397f63bbe6f640777d29c --- /dev/null +++ b/third_party/libobjc/spinlock.h @@ -0,0 +1,81 @@ +#ifdef __MINGW32__ +#include <windows.h> +static unsigned sleep(unsigned seconds) +{ + Sleep(seconds*1000); + return 0; +} +#else +#include <unistd.h> +#endif + +/** + * Number of spinlocks. This allocates one page on 32-bit platforms. + */ +#define spinlock_count (1<<10) +static const int spinlock_mask = spinlock_count - 1; +/** + * Integers used as spinlocks for atomic property access. + */ +extern int spinlocks[spinlock_count]; +/** + * Get a spin lock from a pointer. We want to prevent lock contention between + * properties in the same object - if someone is stupid enough to be using + * atomic property access, they are probably stupid enough to do it for + * multiple properties in the same object. We also want to try to avoid + * contention between the same property in different objects, so we can't just + * use the ivar offset. + */ +static inline volatile int *lock_for_pointer(void *ptr) +{ + intptr_t hash = (intptr_t)ptr; + // Most properties will be pointers, so disregard the lowest few bits + hash >>= sizeof(void*) == 4 ? 2 : 8; + intptr_t low = hash & spinlock_mask; + hash >>= 16; + hash |= low; + return spinlocks + (hash & spinlock_mask); +} + +/** + * Unlocks the spinlock. This is not an atomic operation. We are only ever + * modifying the lowest bit of the spinlock word, so it doesn't matter if this + * is two writes because there is no contention among the high bit. There is + * no possibility of contention among calls to this, because it may only be + * called by the thread owning the spin lock. + */ +inline static void unlock_spinlock(volatile int *spinlock) +{ + __sync_synchronize(); + *spinlock = 0; +} +/** + * Attempts to lock a spinlock. This is heavily optimised for the uncontended + * case, because property access should (generally) not be contended. In the + * uncontended case, this is a single atomic compare and swap instruction and a + * branch. Atomic CAS is relatively expensive (can be a pipeline flush, and + * may require locking a cache line in a cache-coherent SMP system, but it's a + * lot cheaper than a system call). + * + * If the lock is contended, then we just sleep and then try again after the + * other threads have run. Note that there is no upper bound on the potential + * running time of this function, which is one of the great many reasons that + * using atomic accessors is a terrible idea, but in the common case it should + * be very fast. + */ +inline static void lock_spinlock(volatile int *spinlock) +{ + int count = 0; + // Set the spin lock value to 1 if it is 0. + while(!__sync_bool_compare_and_swap(spinlock, 0, 1)) + { + count++; + if (0 == count % 10) + { + // If it is already 1, let another thread play with the CPU for a + // bit then try again. + sleep(0); + } + } +} + diff --git a/third_party/libobjc/statics_loader.c b/third_party/libobjc/statics_loader.c new file mode 100644 index 0000000000000000000000000000000000000000..6b2b1d40a2660cc7fd4817ff5ddffe8ce5aa8326 --- /dev/null +++ b/third_party/libobjc/statics_loader.c @@ -0,0 +1,72 @@ +#include <string.h> +#include <stdio.h> +#include "objc/runtime.h" +#include "module.h" +#include "constant_string.h" +#include "visibility.h" + +#define BUFFER_TYPE struct objc_static_instance_list +#include "buffer.h" + +static BOOL try_init_statics(struct objc_static_instance_list *statics) +{ + const char *class_name = statics->class_name; + + // This is a horrible hack. + // + // Very bad things happen when you have more than one constant string class + // used in a program. Unfortunately, GCC defaults to using + // NXConstantString, and if you forget to specify + // -fconstant-string-class=NSConstantString for some compilation units then + // you will end up with some NSConstantString instances and some + // NXConstantString instances. This is a mess. We hack around this by + // silently assuming that the user meant NSConstantString when they said + // NXConstantString if NSConstantString is set as the constant string class + // in string_class.h or by an external -D flag. + if (strcmp(class_name, "NXConstantString") == 0) + { + class_name = CONSTANT_STRING_CLASS; + } + + Class class = (Class)objc_getClass(class_name); + + if (Nil == class) + { + return NO; + } + for (id *instance=statics->instances ; nil!=*instance ; instance++) + { + (*instance)->isa = class; + } + return YES; +} +PRIVATE void objc_init_statics(struct objc_static_instance_list *statics) +{ + if (!try_init_statics(statics)) + { + set_buffered_object_at_index(statics, buffered_objects++); + } +} + +PRIVATE void objc_init_buffered_statics(void) +{ + BOOL shouldReshuffle = NO; + + for (unsigned i=0 ; i<buffered_objects ; i++) + { + struct objc_static_instance_list *c = buffered_object_at_index(i); + if (NULL != c) + { + if (try_init_statics(c)) + { + set_buffered_object_at_index(NULL, i); + shouldReshuffle = YES; + } + } + } + + if (shouldReshuffle) + { + compact_buffer(); + } +} diff --git a/third_party/libobjc/string_hash.h b/third_party/libobjc/string_hash.h new file mode 100644 index 0000000000000000000000000000000000000000..d86ed0bf0df365caea4c421c8790fb6ea67fe6eb --- /dev/null +++ b/third_party/libobjc/string_hash.h @@ -0,0 +1,34 @@ +#include <string.h> +#include <stdint.h> + +/** + * Efficient string hash function. + */ +__attribute__((unused)) +static uint32_t string_hash(const char *str) +{ + uint32_t hash = 0; + int32_t c; + while ((c = *str++)) + { + hash = c + (hash << 6) + (hash << 16) - hash; + } + return hash; +} + +/** + * Test two strings for equality. + */ +__attribute__((unused)) +static int string_compare(const char *str1, const char *str2) +{ + if (str1 == str2) + { + return 1; + } + if (str1 == NULL || str2 == NULL) + { + return 0; + } + return strcmp(str1, str2) == 0; +} diff --git a/third_party/libobjc/toydispatch.c b/third_party/libobjc/toydispatch.c new file mode 100644 index 0000000000000000000000000000000000000000..c6c697037adaa1c689c864470a1463d5a026d849 --- /dev/null +++ b/third_party/libobjc/toydispatch.c @@ -0,0 +1,240 @@ +#include <pthread.h> +#include <stdlib.h> +#define __TOY_DISPATCH__ +#include "objc/toydispatch.h" + +/** + * Amount of total space in the ring buffer. Must be a power of two. + */ +#define RING_BUFFER_SIZE 32 +/** + * Mask for converting a free-running counters into ring buffer indexes. + */ +#define RING_BUFFER_MASK (RING_BUFFER_SIZE - 1) + +struct dispatch_queue +{ + /** + * Reference count for this queue. + */ + int refcount; + /** + * Spin lock value. Set to 1 when the queue is locked. This allows + * multiple threads to write to the queue but only one to read from it. + * Reading and writing can happen concurrently, but writing requires + * acquisition of this lock. + */ + volatile int spinlock; + /** + * Producer free-running counter. Incremented every time that a new item + * is inserted into the ring buffer. + */ + unsigned int producer; + /** + * Consumer free-running counter. Incremented every time that an item is + * removed from the buffer. + */ + unsigned int consumer; + /** + * Mutex used to protect the condition variable. + */ + pthread_mutex_t mutex; + /** + * Condition variable used in blocking mode. The consumer thread will + * sleep on this condition variable when the queue has been empty for a + * little while. The next producer thread to insert something will poke + * the condition variable on any empty->non-empty transition. + */ + pthread_cond_t conditionVariable; + /** + * Ring buffer containing functions and data to be executed by the + * consumer. + */ + struct + { + dispatch_function_t function; + void *data; + } ring_buffer[RING_BUFFER_SIZE]; +}; + +/** + * Check how much space is in the queue. The number of used elements in the + * queue is always equal to producer - consumer. Producer will always + * overflow before consumer (because you can't remove objects that have not + * been inserted. In this case, the subtraction will be something along the + * lines of (0 - (2^32 - 14)). This will be -(2^32 - 14), however this value + * can't be represented in a 32-bit integer and so will overflow to 14, giving + * the correct result, irrespective of overflow. + */ +#define SPACE(q) (RING_BUFFER_SIZE - (q->producer - q->consumer)) +/** + * The buffer is full if there is no space in it. + */ +#define ISFULL(q) (SPACE(q) == 0) +/** + * The buffer is empty if there is no data in it. + */ +#define ISEMPTY(q) ((q->producer - q->consumer) == 0) +/** + * Converting the free running counters to array indexes is a masking + * operation. For this to work, the buffer size must be a power of two. + * RING_BUFFER_MASK = RING_BUFFER_SIZE - 1. If RING_BUFFER_SIZE is 256, we want the lowest 8 + * bits of the index, which is obtained by ANDing the value with 255. Any + * power of two may be selected. Non power-of-two values could be used if a + * more complex mapping operation were chosen, but this one is nice and cheap. + */ +#define MASK(index) ((index) & RING_BUFFER_MASK) + +/** + * Lock the queue. This uses a very lightweight, nonrecursive, spinlock. It + * is expected that queue insertions will be relatively uncontended. + */ +inline static void lock_queue(dispatch_queue_t queue) +{ + // Set the spin lock value to 1 if it is 0. + while(!__sync_bool_compare_and_swap(&queue->spinlock, 0, 1)) + { + // If it is already 1, let another thread play with the CPU for a bit + // then try again. + sched_yield(); + } +} + +/** + * Unlock the queue. This doesn't need to be an atomic op; that will cause a + * complete pipeline flush on this thread and not actually buy us anything + * because at this point only one thread (this one) will do anything that will + * modify the variable. The other threads will all be using atomic + * compare-and-exchange instructions which will fail because we already set it + * to 1. + */ +inline static void unlock_queue(dispatch_queue_t queue) +{ + queue->spinlock = 0; +} + +/** + * Inserting an element into the queue involves the following steps: + * + * 1) Check that there is space in the buffer. + * Spin if there isn't any. + * 2) Add the invocation and optionally the proxy containing the return value + * (nil for none) to the next two elements in the ring buffer. + * 3) Increment the producer counter (by two, since we are adding two elements). + * 4) If the queue was previously empty, we need to transition back to lockless + * mode. This is done by signalling the condition variable that the other + * thread will be waiting on if it is in blocking mode. + */ +inline static void insert_into_queue(dispatch_queue_t queue, + dispatch_function_t function, + void *data) +{ + /* Wait for space in the buffer */ + lock_queue(queue); + while (ISFULL(queue)) + { + sched_yield(); + } + unsigned int idx = MASK(queue->producer); + queue->ring_buffer[idx].function = function; + queue->ring_buffer[idx].data = data; + // NOTE: This doesn't actually need to be atomic on a strongly-ordered + // architecture like x86. + __sync_fetch_and_add(&queue->producer, 1); + unsigned int space = queue->producer - queue->consumer; + unlock_queue(queue); + // If we've just transitioned from empty to full, wake up the consumer thread. + // Note: We do this after unlocking the queue, because it is much more + // expensive than anything else that we do in this function and we don't + // want to hold the spinlock for any longer than possible. We need to + // calculate the space first, however, because otherwise another thread may + // increment producer, while consumer stays the same (with the consumer + // thread sleeping), preventing the wakeup. + if (space == 1) + { + pthread_mutex_lock(&queue->mutex); + pthread_cond_signal(&queue->conditionVariable); + pthread_mutex_unlock(&queue->mutex); + } +} +/** + * Removing an element from the queue involves the following steps: + * + * 1) Wait until the queue has messages waiting. If there are none, enter + * blocking mode. The additional test inside the mutex ensures that a + * transition from blocking to non-blocking mode will not be missed, since the + * condition variable can only be signalled when the producer thread has the + * mutex. + * 2) Read the invocation and return proxy from the buffer. + * 3) Incrememt the consumer counter. + */ +static inline void read_from_queue(dispatch_queue_t queue, + dispatch_function_t *function, void **data) +{ + while (ISEMPTY(queue)) + { + pthread_mutex_lock(&queue->mutex); + if (ISEMPTY(queue)) + { + pthread_cond_wait(&queue->conditionVariable, &queue->mutex); + } + pthread_mutex_unlock(&queue->mutex); + } + unsigned int idx = MASK(queue->consumer); + *function = queue->ring_buffer[idx].function; + *data = queue->ring_buffer[idx].data; + __sync_fetch_and_add(&queue->consumer, 1); +} + +static void *runloop(void *q) +{ + dispatch_queue_t queue = q; + dispatch_function_t function; + void *data; + while (queue->refcount > 0) + { + read_from_queue(queue, &function, &data); + function(data); + } + pthread_cond_destroy(&queue->conditionVariable); + pthread_mutex_destroy(&queue->mutex); + free(queue); + return NULL; +} + + +dispatch_queue_t dispatch_queue_create(const char *label, + void *attr) +{ + dispatch_queue_t queue = calloc(1, sizeof(struct dispatch_queue)); + queue->refcount = 1; + pthread_cond_init(&queue->conditionVariable, NULL); + pthread_mutex_init(&queue->mutex, NULL); + pthread_t thread; + pthread_create(&thread, NULL, runloop, queue); + pthread_detach(thread); + return queue; +} + +void dispatch_async_f(dispatch_queue_t queue, void *context, + dispatch_function_t work) +{ + insert_into_queue(queue, work, context); +} + +static void release(void *queue) +{ + ((dispatch_queue_t)queue)->refcount--; +} + +void dispatch_release(dispatch_queue_t queue) +{ + // Asynchronously release the queue, so that we don't delete it before all + // of the work is finished. + insert_into_queue(queue, release, queue); +} + +void dispatch_retain(dispatch_queue_t queue) +{ + queue->refcount++; +} diff --git a/third_party/libobjc/type_encoding_cases.h b/third_party/libobjc/type_encoding_cases.h new file mode 100644 index 0000000000000000000000000000000000000000..36ab51651b4e73abb90da07d8542d177be6ed39f --- /dev/null +++ b/third_party/libobjc/type_encoding_cases.h @@ -0,0 +1,35 @@ +/** + * type_encoding_cases.h - expects the APPLY_TYPE macro to be defined. This + * macro is invoked once for every type and its Objective-C name. Use this + * file when implementing things like the -unsignedIntValue family of methods. + * For this case, the macro will be invoked with unsigned int as the type and + * unsignedInt as the name. + */ +#ifndef APPLY_TYPE +#error Define APPLY_TYPE(type, name, capitalizedName, encodingChar) before including this file +#endif +APPLY_TYPE(double, double, Double, 'd') +APPLY_TYPE(float, float, Float, 'f') +APPLY_TYPE(signed char, char, Char, 'c') +APPLY_TYPE(int, int, Int, 'i') +APPLY_TYPE(short, short, Short, 's') +APPLY_TYPE(long, long, Long, 'l') +APPLY_TYPE(long long, longLong, LongLong, 'q') +//APPLY_TYPE(__int128, int128, Int128, 't') +APPLY_TYPE(unsigned char, unsignedChar, UnsignedChar, 'C') +APPLY_TYPE(unsigned short, unsignedShort, UnsignedShort, 'S') +APPLY_TYPE(unsigned int, unsignedInt, UnsignedInt, 'I') +APPLY_TYPE(unsigned long, unsignedLong, UnsignedLong, 'L') +APPLY_TYPE(unsigned long long, unsignedLongLong, UnsignedLongLong, 'Q') +//APPLY_TYPE(unsigned __int128, unsignedInt128, UnsignedInt128, 'T') +#ifdef NON_INTEGER_TYPES +#undef NON_INTEGER_TYPES +APPLY_TYPE(_Bool, bool, Bool, 'B') +#ifndef SKIP_ID +APPLY_TYPE(id, object, Object, '@') +#endif +APPLY_TYPE(Class, class, Class, '#') +APPLY_TYPE(SEL, selector, Selector, ':') +APPLY_TYPE(char*, cString, CString, '*') +#endif +#undef APPLY_TYPE diff --git a/third_party/libobjc/unistd.h b/third_party/libobjc/unistd.h new file mode 100644 index 0000000000000000000000000000000000000000..f0ce70d66bc68ffbe0b4a810590c680c5276d2ef --- /dev/null +++ b/third_party/libobjc/unistd.h @@ -0,0 +1,8 @@ +/* See http://llvm.org/bugs/show_bug.cgi?id=4746 */ +#ifdef __block +# undef __block +# include_next "unistd.h" +# define __block __attribute__((__blocks__(byref))) +#else +# include_next "unistd.h" +#endif diff --git a/third_party/libobjc/unwind-arm.h b/third_party/libobjc/unwind-arm.h new file mode 100644 index 0000000000000000000000000000000000000000..63774611ca185fc938a07480e7e4ee8dc4633942 --- /dev/null +++ b/third_party/libobjc/unwind-arm.h @@ -0,0 +1,195 @@ +/** + * ARM-specific unwind definitions. These are taken from the ARM EHABI + * specification. + */ + typedef enum +{ + _URC_OK = 0, /* operation completed successfully */ + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, + _URC_FAILURE = 9, /* unspecified failure of some kind */ + _URC_FATAL_PHASE1_ERROR = _URC_FAILURE +} _Unwind_Reason_Code; + +typedef uint32_t _Unwind_State; +#ifdef __clang__ +static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; +static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; +static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; +#else // GCC fails at knowing what a constant expression is +# define _US_VIRTUAL_UNWIND_FRAME 0 +# define _US_UNWIND_FRAME_STARTING 1 +# define _US_UNWIND_FRAME_RESUME 2 +#endif + +typedef struct _Unwind_Context _Unwind_Context; + +typedef uint32_t _Unwind_EHT_Header; + +struct _Unwind_Exception +{ + uint64_t exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code, struct _Unwind_Exception *); + /* Unwinder cache, private fields for the unwinder's use */ + struct + { + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + /* init reserved1 to 0, then don't touch */ + } unwinder_cache; + /* Propagation barrier cache (valid after phase 1): */ + struct + { + uint32_t sp; + uint32_t bitpattern[5]; + } barrier_cache; + /* Cleanup cache (preserved over cleanup): */ + struct + { + uint32_t bitpattern[4]; + } cleanup_cache; + /* Pr cache (for pr's benefit): */ + struct + { + /** function start address */ + uint32_t fnstart; + /** pointer to EHT entry header word */ + _Unwind_EHT_Header *ehtp; + /** additional data */ + uint32_t additional; + uint32_t reserved1; + } pr_cache; + /** Force alignment of next item to 8-byte boundary */ + long long int :0; +}; + +/* Unwinding functions */ +_Unwind_Reason_Code _Unwind_RaiseException(struct _Unwind_Exception *ucbp); +void _Unwind_Resume(struct _Unwind_Exception *ucbp); +void _Unwind_Complete(struct _Unwind_Exception *ucbp); +void _Unwind_DeleteException(struct _Unwind_Exception *ucbp); +void *_Unwind_GetLanguageSpecificData(struct _Unwind_Context*); + +typedef enum +{ + _UVRSR_OK = 0, + _UVRSR_NOT_IMPLEMENTED = 1, + _UVRSR_FAILED = 2 +} _Unwind_VRS_Result; +typedef enum +{ + _UVRSC_CORE = 0, + _UVRSC_VFP = 1, + _UVRSC_WMMXD = 3, + _UVRSC_WMMXC = 4 +} _Unwind_VRS_RegClass; +typedef enum +{ + _UVRSD_UINT32 = 0, + _UVRSD_VFPX = 1, + _UVRSD_UINT64 = 3, + _UVRSD_FLOAT = 4, + _UVRSD_DOUBLE = 5 +} _Unwind_VRS_DataRepresentation; + +_Unwind_VRS_Result _Unwind_VRS_Get(_Unwind_Context *context, + _Unwind_VRS_RegClass regclass, + uint32_t regno, + _Unwind_VRS_DataRepresentation representation, + void *valuep); +_Unwind_VRS_Result _Unwind_VRS_Set(_Unwind_Context *context, + _Unwind_VRS_RegClass regclass, + uint32_t regno, + _Unwind_VRS_DataRepresentation representation, + void *valuep); + +/* Return the base-address for data references. */ +extern unsigned long _Unwind_GetDataRelBase(struct _Unwind_Context *); + +/* Return the base-address for text references. */ +extern unsigned long _Unwind_GetTextRelBase(struct _Unwind_Context *); +extern unsigned long _Unwind_GetRegionStart(struct _Unwind_Context *); + +/** + * The next set of functions are compatibility extensions, implementing Itanium + * ABI functions on top of ARM ones. + */ + +#define _UA_SEARCH_PHASE 1 +#define _UA_CLEANUP_PHASE 2 +#define _UA_HANDLER_FRAME 4 +#define _UA_FORCE_UNWIND 8 + +static inline unsigned long _Unwind_GetGR(struct _Unwind_Context *context, int reg) +{ + unsigned long val; + _Unwind_VRS_Get(context, _UVRSC_CORE, reg, _UVRSD_UINT32, &val); + return val; +} +static inline void _Unwind_SetGR(struct _Unwind_Context *context, int reg, unsigned long val) +{ + _Unwind_VRS_Set(context, _UVRSC_CORE, reg, _UVRSD_UINT32, &val); +} +static inline unsigned long _Unwind_GetIP(_Unwind_Context *context) +{ + // Low bit store the thumb state - discard it + return _Unwind_GetGR(context, 15) & ~1; +} +static inline void _Unwind_SetIP(_Unwind_Context *context, unsigned long val) +{ + // The lowest bit of the instruction pointer indicates whether we're in + // thumb or ARM mode. This is assumed to be fixed throughout a function, + // so must be propagated when setting the program counter. + unsigned long thumbState = _Unwind_GetGR(context, 15) & 1; + _Unwind_SetGR(context, 15, (val | thumbState)); +} + +/** GNU API function that unwinds the frame */ +_Unwind_Reason_Code __gnu_unwind_frame(struct _Unwind_Exception*, struct _Unwind_Context*); + + +#define DECLARE_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(_Unwind_State state,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context); + +#define BEGIN_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(_Unwind_State state,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context)\ +{\ + int version = 1;\ + uint64_t exceptionClass = exceptionObject->exception_class;\ + int actions;\ + switch (state)\ + {\ + default: return _URC_FAILURE;\ + case _US_VIRTUAL_UNWIND_FRAME:\ + {\ + actions = _UA_SEARCH_PHASE;\ + break;\ + }\ + case _US_UNWIND_FRAME_STARTING:\ + {\ + actions = _UA_CLEANUP_PHASE;\ + if (exceptionObject->barrier_cache.sp == _Unwind_GetGR(context, 13))\ + {\ + actions |= _UA_HANDLER_FRAME;\ + }\ + break;\ + }\ + case _US_UNWIND_FRAME_RESUME:\ + {\ + return continueUnwinding(exceptionObject, context);\ + break;\ + }\ + }\ + _Unwind_SetGR (context, 12, (unsigned long)exceptionObject); + +#define CALL_PERSONALITY_FUNCTION(name) name(state,exceptionObject,context) diff --git a/third_party/libobjc/unwind-itanium.h b/third_party/libobjc/unwind-itanium.h new file mode 100644 index 0000000000000000000000000000000000000000..16b3eed6d700e094b3eb7b9896f075c8baeac274 --- /dev/null +++ b/third_party/libobjc/unwind-itanium.h @@ -0,0 +1,170 @@ +/* libunwind - a platform-independent unwind library + Copyright (C) 2003 Hewlett-Packard Co + Contributed by David Mosberger-Tang <davidm@hpl.hp.com> + +This file is part of libunwind. + +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. */ + +#ifndef _UNWIND_H +#define _UNWIND_H + +/* For uint64_t */ +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Minimal interface as per C++ ABI draft standard: + + http://www.codesourcery.com/cxx-abi/abi-eh.html */ + +typedef enum + { + _URC_NO_REASON = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8 + } +_Unwind_Reason_Code; + +typedef int _Unwind_Action; + +#define _UA_SEARCH_PHASE 1 +#define _UA_CLEANUP_PHASE 2 +#define _UA_HANDLER_FRAME 4 +#define _UA_FORCE_UNWIND 8 + +struct _Unwind_Context; /* opaque data-structure */ +struct _Unwind_Exception; /* forward-declaration */ + +typedef void (*_Unwind_Exception_Cleanup_Fn) (_Unwind_Reason_Code, + struct _Unwind_Exception *); + +typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) (int, _Unwind_Action, + uint64_t, + struct _Unwind_Exception *, + struct _Unwind_Context *, + void *); + +/* The C++ ABI requires exception_class, private_1, and private_2 to + be of type uint64 and the entire structure to be + double-word-aligned. Please note that exception_class stays 64-bit + even on 32-bit machines for gcc compatibility. */ +struct _Unwind_Exception + { + uint64_t exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + unsigned long private_1; + unsigned long private_2; + } __attribute__((__aligned__)); + +extern _Unwind_Reason_Code _Unwind_RaiseException (struct _Unwind_Exception *); +extern _Unwind_Reason_Code _Unwind_ForcedUnwind (struct _Unwind_Exception *, + _Unwind_Stop_Fn, void *); +extern void _Unwind_Resume (struct _Unwind_Exception *); +extern void _Unwind_DeleteException (struct _Unwind_Exception *); +extern unsigned long _Unwind_GetGR (struct _Unwind_Context *, int); +extern void _Unwind_SetGR (struct _Unwind_Context *, int, unsigned long); +extern unsigned long _Unwind_GetIP (struct _Unwind_Context *); +extern unsigned long _Unwind_GetIPInfo (struct _Unwind_Context *, int *); +extern void _Unwind_SetIP (struct _Unwind_Context *, unsigned long); +extern unsigned long _Unwind_GetLanguageSpecificData (struct _Unwind_Context*); +extern unsigned long _Unwind_GetRegionStart (struct _Unwind_Context *); + +#ifdef _GNU_SOURCE + +/* Callback for _Unwind_Backtrace(). The backtrace stops immediately + if the callback returns any value other than _URC_NO_REASON. */ +typedef _Unwind_Reason_Code (*_Unwind_Trace_Fn) (struct _Unwind_Context *, + void *); + +/* See http://gcc.gnu.org/ml/gcc-patches/2001-09/msg00082.html for why + _UA_END_OF_STACK exists. */ +# define _UA_END_OF_STACK 16 + +/* If the unwind was initiated due to a forced unwind, resume that + operation, else re-raise the exception. This is used by + __cxa_rethrow(). */ +extern _Unwind_Reason_Code + _Unwind_Resume_or_Rethrow (struct _Unwind_Exception *); + +/* See http://gcc.gnu.org/ml/gcc-patches/2003-09/msg00154.html for why + _Unwind_GetBSP() exists. */ +extern unsigned long _Unwind_GetBSP (struct _Unwind_Context *); + +/* Return the "canonical frame address" for the given context. + This is used by NPTL... */ +extern unsigned long _Unwind_GetCFA (struct _Unwind_Context *); + +/* Return the base-address for data references. */ +extern unsigned long _Unwind_GetDataRelBase (struct _Unwind_Context *); + +/* Return the base-address for text references. */ +extern unsigned long _Unwind_GetTextRelBase (struct _Unwind_Context *); + +/* Call _Unwind_Trace_Fn once for each stack-frame, without doing any + cleanup. The first frame for which the callback is invoked is the + one for the caller of _Unwind_Backtrace(). _Unwind_Backtrace() + returns _URC_END_OF_STACK when the backtrace stopped due to + reaching the end of the call-chain or _URC_FATAL_PHASE1_ERROR if it + stops for any other reason. */ +extern _Unwind_Reason_Code _Unwind_Backtrace (_Unwind_Trace_Fn, void *); + +/* Find the start-address of the procedure containing the specified IP + or NULL if it cannot be found (e.g., because the function has no + unwind info). Note: there is not necessarily a one-to-one + correspondence between source-level functions and procedures: some + functions don't have unwind-info and others are split into multiple + procedures. */ +extern void *_Unwind_FindEnclosingFunction (void *); + +/* See also Linux Standard Base Spec: + http://www.linuxbase.org/spec/refspecs/LSB_1.3.0/gLSB/gLSB/libgcc-s.html */ + +#endif /* _GNU_SOURCE */ + +#define DECLARE_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(int version,\ + _Unwind_Action actions,\ + uint64_t exceptionClass,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context); +#define BEGIN_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(int version,\ + _Unwind_Action actions,\ + uint64_t exceptionClass,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context)\ +{ + +#define CALL_PERSONALITY_FUNCTION(name) name(version, actions, exceptionClass, exceptionObject, context) + +#ifdef __cplusplus +} +#endif + +#endif /* _UNWIND_H */ diff --git a/third_party/libobjc/unwind.h b/third_party/libobjc/unwind.h new file mode 100644 index 0000000000000000000000000000000000000000..76b678048517d19c6ffdd1a41ca37eb5ba2a732c --- /dev/null +++ b/third_party/libobjc/unwind.h @@ -0,0 +1,5 @@ +#ifdef __arm__ +#include "unwind-arm.h" +#else +#include "unwind-itanium.h" +#endif diff --git a/third_party/libobjc/visibility.h b/third_party/libobjc/visibility.h new file mode 100644 index 0000000000000000000000000000000000000000..86c379b0001b94026b4a6c6475461862469fcfcd --- /dev/null +++ b/third_party/libobjc/visibility.h @@ -0,0 +1,24 @@ +#if defined _WIN32 || defined __CYGWIN__ +# define PUBLIC __attribute__((dllexport)) +# define PRIVATE +#else +# define PUBLIC __attribute__ ((visibility("default"))) +# define PRIVATE __attribute__ ((visibility("hidden"))) +#endif +#ifdef NO_LEGACY +# define LEGACY PRIVATE +#else +# define LEGACY PUBLIC +#endif + +#if defined(DEBUG) || (!defined(__clang__)) +# include <assert.h> +# define UNREACHABLE(x) assert(0 && x) +# define ASSERT(x) assert(x) +#else +# define UNREACHABLE(x) __builtin_unreachable() +# define ASSERT(x) do { if (x) __builtin_unreachable(); } while(0) +#endif + +#define LIKELY(x) __builtin_expect(x, 1) +#define UNLIKELY(x) __builtin_expect(x, 0)