iOS LLVM 中的宏定义
在阅读 Objc 库源码时常常会遇到很多宏定义,比如宏 SUPPORT_INDEXED_ISA、SUPPORT_PACKED_ISA,代码如下所示:
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa
// field as a maskable pointer with other data around it.
#if (!__LP64__ || TARGET_OS_WIN32 || \
(TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST && !__arm64__))
# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif
在上面的宏定义中,__ARM_ARCH_7K__、__arm64__、__LP64__ 这些宏在 Objc 库中找不到定义的源码。如果不清楚这些宏的意义,对阅读源码会带来一定的障碍。实际上,这些宏都定义在 LLVM 源码中(基本上找不到定义的宏,都可以在 LLVM 源码中找到)。
__ARM_ARCH_7K__
在 LLVM 源码 ARM.cpp 中,可以看到对 __ARM_ARCH_7K__ 的定义,源码如下:
// file: ARM.cpp // Unfortunately, __ARM_ARCH_7K__ is now more of an ABI descriptor. The CPU
// happens to be Cortex-A7 though, so it should still get __ARM_ARCH_7A__.
if (getTriple().isWatchABI()) // 判断是否是 Watch 的ABI
Builder.defineMacro("__ARM_ARCH_7K__", "2");
从源码看到,这个宏是在 Apple Watch 下生效,在 iPhone 设备上该宏不会生效。
__arm64__
在 LLVM 源码 AArch64.cpp 中,可以看到对 __arm64__ 的定义,源码如下:
void DarwinAArch64TargetInfo::getOSDefines(const LangOptions &Opts,
const llvm::Triple &Triple,
MacroBuilder &Builder) const {
Builder.defineMacro("__AARCH64_SIMD__");
if (Triple.isArch32Bit())
Builder.defineMacro("__ARM64_ARCH_8_32__");
else
Builder.defineMacro("__ARM64_ARCH_8__");
Builder.defineMacro("__ARM_NEON__");
Builder.defineMacro("__LITTLE_ENDIAN__");
Builder.defineMacro("__REGISTER_PREFIX__", "");
Builder.defineMacro("__arm64", "1");
Builder.defineMacro("__arm64__", "1"); // __arm64__ 定义 if (Triple.isArm64e())
Builder.defineMacro("__arm64e__", "1"); getDarwinDefines(Builder, Opts, Triple, PlatformName, PlatformMinVersion);
}
从源码上可以看到,只要 ARM CPU 是 64bit,就会定义 __arm64__ 宏,虽然可能这个 CPU 使用的是 ILP32(见下文)。
__LP64__
在 LLVM 源码 InitPreprocessor.cpp 中,可以看到对 __LP64__ 的定义,源码如下:
// file: InitPreprocessor.cpp
static void InitializePredefinedMacros(const TargetInfo &TI,
const LangOptions &LangOpts,
const FrontendOptions &FEOpts,
const PreprocessorOptions &PPOpts,
MacroBuilder &Builder) {
... if (TI.getPointerWidth(0) == 64 && TI.getLongWidth() == 64
&& TI.getIntWidth() == 32) {
Builder.defineMacro("_LP64");
Builder.defineMacro("__LP64__"); // 定义 __LP64__
} if (TI.getPointerWidth(0) == 32 && TI.getLongWidth() == 32
&& TI.getIntWidth() == 32) {
Builder.defineMacro("_ILP32");
Builder.defineMacro("__ILP32__"); // 定义 __ILP32__
} ... // Get other target #defines.
TI.getTargetDefines(LangOpts, Builder); // 该方法会重新定义 __LP64__
}
从上面源码可以看到,如果指针 pointer 的长度是 64bit,long 类型的长度是 64bit,int 类型的长度是 32bit,那么就定义宏 __LP64__。
如果指针 pointer 的长度是 32bit,long 类型的长度是 32bit,int 类型的长度是 32bit,那么就定义宏 __ILP32__。
源码最后一行 TI.getTargetDefines(LangOpts, Builder) 在 ARM 架构下重新定义 __LP64__,相关源码位于 AArch64.cpp:
// file: AArch64.cpp
void AArch64TargetInfo::getTargetDefines(const LangOptions &Opts,
MacroBuilder &Builder) const {
// Target identification.
Builder.defineMacro("__aarch64__");
// For bare-metal.
if (getTriple().getOS() == llvm::Triple::UnknownOS &&
getTriple().isOSBinFormatELF())
Builder.defineMacro("__ELF__"); // Target properties.
if (!getTriple().isOSWindows() && getTriple().isArch64Bit()) { // 在非 Windows 下,并且真正支持 64bit 指针的 CPU 架构下才定义 __LP64__
Builder.defineMacro("_LP64");
Builder.defineMacro("__LP64__");
} ... }
从上面的源码可以看到,非 Windows 系统 & 真正支持 64bit 指针的 CPU 架构才会定义 __LP64__。那么哪些类型的 CPU 支持 64bit 的指针呢? 通过查看 Triple::isArch64Bit 方法可以得到答案:
// file: Triple.cpp
bool Triple::isArch64Bit() const {
return getArchPointerBitWidth(getArch()) == 64;
}
static unsigned getArchPointerBitWidth(llvm::Triple::ArchType Arch) {
switch (Arch) {
case llvm::Triple::UnknownArch:
return 0;
case llvm::Triple::avr:
case llvm::Triple::msp430:
return 16;
case llvm::Triple::aarch64_32:
case llvm::Triple::amdil:
case llvm::Triple::arc:
case llvm::Triple::arm:
case llvm::Triple::armeb:
case llvm::Triple::csky:
case llvm::Triple::dxil:
case llvm::Triple::hexagon:
case llvm::Triple::hsail:
case llvm::Triple::kalimba:
case llvm::Triple::lanai:
case llvm::Triple::le32:
case llvm::Triple::loongarch32:
case llvm::Triple::m68k:
case llvm::Triple::mips:
case llvm::Triple::mipsel:
case llvm::Triple::nvptx:
case llvm::Triple::ppc:
case llvm::Triple::ppcle:
case llvm::Triple::r600:
case llvm::Triple::renderscript32:
case llvm::Triple::riscv32:
case llvm::Triple::shave:
case llvm::Triple::sparc:
case llvm::Triple::sparcel:
case llvm::Triple::spir:
case llvm::Triple::spirv32:
case llvm::Triple::tce:
case llvm::Triple::tcele:
case llvm::Triple::thumb:
case llvm::Triple::thumbeb:
case llvm::Triple::wasm32:
case llvm::Triple::x86:
case llvm::Triple::xcore:
return 32;
case llvm::Triple::aarch64:
case llvm::Triple::aarch64_be:
case llvm::Triple::amdgcn:
case llvm::Triple::amdil64:
case llvm::Triple::bpfeb:
case llvm::Triple::bpfel:
case llvm::Triple::hsail64:
case llvm::Triple::le64:
case llvm::Triple::loongarch64:
case llvm::Triple::mips64:
case llvm::Triple::mips64el:
case llvm::Triple::nvptx64:
case llvm::Triple::ppc64:
case llvm::Triple::ppc64le:
case llvm::Triple::renderscript64:
case llvm::Triple::riscv64:
case llvm::Triple::sparcv9:
case llvm::Triple::spir64:
case llvm::Triple::spirv64:
case llvm::Triple::systemz:
case llvm::Triple::ve:
case llvm::Triple::wasm64:
case llvm::Triple::x86_64:
return 64;
}
llvm_unreachable("Invalid architecture value");
}
上面源码需要注意的一个 CPU 架构是 aarch64_32,这种 ARM 架构的 CPU 虽然是 64bit 的,但是 int、long、pointer 都使用 32bit 表示(即 ILP32)。这种 CPU 通常用在嵌入式里面,Apple Watch Series 4/5 就是使用的这种 CPU:

由于 Apple 从 iPhone 5S 就开始支持 64bit 的 CPU,因此在 >= iPhone 5S 的设备上,SUPPORT_INDEXED_ISA 定义为0,SUPPORT_PACKED_ISA 定义为1。
__OBJC__
___OBJC__ 宏定义在 LLVM 源码的 InitPreprocessor.cpp 文件,源码如下:
// file: InitPreprocessor.cpp
static void InitializeStandardPredefinedMacros(const TargetInfo &TI,
const LangOptions &LangOpts,
const FrontendOptions &FEOpts,
MacroBuilder &Builder) {
... if (LangOpts.ObjC)
Builder.defineMacro("__OBJC__"); // 定义 __OBJC__ 宏 ...
}
从源码可以看到,如果编译的语言是Objective-C,那么这个宏就会被定义。
__OBJC2__
__OBJC2__ 宏定义在 LLVM 源码的 InitPreprocessor.cpp 文件,源码如下:
// file: InitPreprocessor.cpp static void InitializePredefinedMacros(const TargetInfo &TI,
const LangOptions &LangOpts,
const FrontendOptions &FEOpts,
const PreprocessorOptions &PPOpts,
MacroBuilder &Builder) {
... if (LangOpts.ObjC) {
if (LangOpts.ObjCRuntime.isNonFragile()) {
Builder.defineMacro("__OBJC2__"); // 如果是 Objective-C 语言,并且满足 non fragile,就定义 __OBJC2__ ...
}
对于 __OBJC2__ 宏的定义中,除了判断是 Objectvie-C 语言,还需要判断 non-fragile 条件。该条件判断的源码如下:
// file: ObjcRuntime.h
bool isNonFragile() const {
switch (getKind()) {
case FragileMacOSX: return false;
case GCC: return false;
case MacOSX: return true; // Mac
case GNUstep: return true;
case ObjFW: return true;
case iOS: return true; // iOS
case WatchOS: return true; // Watch
}
llvm_unreachable("bad kind");
}
从源码可以看到,对于 iOS 系统和 Watch OS 系统,__OBJC2__ 宏是一定会定义的。但是对于 MAC 系统就要区分 MacOSX 与 FragileMacOSX。这些类型的定义源码如下:
// file: ObjcRuntime.h
class ObjCRuntime {
public:
/// The basic Objective-C runtimes that we know about.
enum Kind {
/// 'macosx' is the Apple-provided NeXT-derived runtime on Mac OS
/// X platforms that use the non-fragile ABI; the version is a
/// release of that OS.
MacOSX,
/// 'macosx-fragile' is the Apple-provided NeXT-derived runtime on
/// Mac OS X platforms that use the fragile ABI; the version is a
/// release of that OS.
FragileMacOSX,
/// 'ios' is the Apple-provided NeXT-derived runtime on iOS or the iOS
/// simulator; it is always non-fragile. The version is a release
/// version of iOS.
iOS,
/// 'watchos' is a variant of iOS for Apple's watchOS. The version
/// is a release version of watchOS.
WatchOS,
/// 'gcc' is the Objective-C runtime shipped with GCC, implementing a
/// fragile Objective-C ABI
GCC,
/// 'gnustep' is the modern non-fragile GNUstep runtime.
GNUstep,
/// 'objfw' is the Objective-C runtime included in ObjFW
ObjFW
};
...
}
__has_feature
__has_feature 宏可以帮助我们判断一个功能是否可以由 Clang 编译器支持,Clang 文档原文如下:
These function-like macros take a single identifier argument that is the name of a feature.
__has_featureevaluates to 1 if the feature is both supported by Clang and standardized in the current language standard or 0 if not
那么它的实现是怎样的呢?
首先 Clang 会注册 __has_feature 宏,注册的结果被保存在 Preprocessor 对象的实例变量 Ident__has_feature 中。源码如下所示:
/// file: PPMacroExpansion.cpp /// RegisterBuiltinMacros - Register builtin macros, such as __LINE__ with the
/// identifier table.
void Preprocessor::RegisterBuiltinMacros() {
// 注入了许多常见的内置宏
Ident__LINE__ = RegisterBuiltinMacro(*this, "__LINE__");
Ident__FILE__ = RegisterBuiltinMacro(*this, "__FILE__");
Ident__DATE__ = RegisterBuiltinMacro(*this, "__DATE__");
Ident__TIME__ = RegisterBuiltinMacro(*this, "__TIME__");
Ident__COUNTER__ = RegisterBuiltinMacro(*this, "__COUNTER__");
Ident_Pragma = RegisterBuiltinMacro(*this, "_Pragma"); ... // Clang Extensions.
Ident__FILE_NAME__ = RegisterBuiltinMacro(*this, "__FILE_NAME__");
Ident__has_feature = RegisterBuiltinMacro(*this, "__has_feature"); // __has_feature 被注入
Ident__has_extension = RegisterBuiltinMacro(*this, "__has_extension"); // __has_extension 被注入
Ident__has_builtin = RegisterBuiltinMacro(*this, "__has_builtin"); // __has_builtin 被注入 ...
}
当 Clang 预编译源文件时如果遇到了 __has_feature 标识符,就会进行扩展,扩展的代码如下所示:
// file: PPMacroExpansion.cpp
void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
...
} else if (II == Ident__has_feature) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this, false,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
return II && HasFeature(*this, II->getName()); // 最终 Clang 编译器调用 HasFeature 函数进行判断
});
} else if (II == Ident__has_extension) {
...
}
...
}
从源码可以看到,Clang 的扩展结果通过调用 HasFeature 函数获取,HasFeature 函数接收要检测的功能名作为参数,源码如下:
// file: PPMacroExpansion.cpp
static bool HasFeature(const Preprocessor &PP, StringRef Feature) {
const LangOptions &LangOpts = PP.getLangOpts();
// Normalize the feature name, __foo__ becomes foo.
if (Feature.startswith("__") && Feature.endswith("__") && Feature.size() >= 4)
Feature = Feature.substr(2, Feature.size() - 4);
#define FEATURE(Name, Predicate) .Case(#Name, Predicate) // 下面的 Feature.def 里面使用了大量的 FEATURE 宏
return llvm::StringSwitch<bool>(Feature) // StringSwitch 是一个类,它支持对字符串进行 switch-case 操作
#include "clang/Basic/Features.def" // 所有 Clang 支持的功能都定义在这个文件
.Default(false); // 默认返回 false
#undef FEATURE
}
从源码可以看到,HasFeature 函数内部定义了一个 FEATURE 宏,这个宏在 Feature.def 文件中被大量使用,下面截取部分 Feature.def 文件内容:
// file: Feature.def ... // Objective-C features
FEATURE(objc_arr, LangOpts.ObjCAutoRefCount) // FIXME: REMOVE?
FEATURE(objc_arc, LangOpts.ObjCAutoRefCount)
FEATURE(objc_arc_fields, true) // ARC
FEATURE(objc_arc_weak, LangOpts.ObjCWeak) // weak
FEATURE(objc_default_synthesize_properties, LangOpts.ObjC)
FEATURE(objc_fixed_enum, LangOpts.ObjC)
FEATURE(objc_instancetype, LangOpts.ObjC) // instancetype
FEATURE(objc_kindof, LangOpts.ObjC) ...
经过宏扩展之后,HasFeature 函数最后的 return 语句实际上变成为:
return llvm::StringSwitch<bool>(Feature)
...
.Case("objc_arr", LangOpts.ObjCAutoRefCount)
.Case("objc_arc", LangOpts.ObjCAutoRefCount)
.Case("objc_arc_fields", true)
.Case("objc_arc_weak", LangOpts.ObjCWeak)
.Case("objc_default_synthesize_properties", LangOpts.ObjC)
.Case("objc_fixed_enum", LangOpts.ObjC)
.Case("objc_instancetype", LangOpts.ObjC)
.Case("objc_kindof", LangOpts.ObjC)
...
.Default(false)
return 语句首先传入待检测的功能名,调用 StringSwitch 的构造函数生成一个 StringSwitch 对象,这个 StringSwitch 对象用来对 string 进行 switch-case 操作,它内部有 Case 和 Default 两个方法,定义如下:
template<typename T, typename R = T>
class StringSwitch {
... // Case-sensitive case matchers
StringSwitch &Case(StringLiteral S, T Value) {
if (!Result && Str == S) { // 如果 switch-case 没有匹配的结果,本次 Case 方法才进行比较,否则如果已经匹配出结果,直接返回对象本身
Result = std::move(Value);
}
return *this; // 返回对象本身,形成链式调用
} .. R Default(T Value) {
if (Result)
return std::move(*Result); // 匹配除了结果,直接返回结果
return Value; // 未匹配出结果,返回默认值
} ...
}
从源码可以看到,Case 方法只有在未匹配出结果时,才进行匹配操作,如果结果已经匹配,Case 方法直接返回对象本身,这样就可以形成链式调用。链式调用最后,会调用到 Default 方法,如果已经匹配到结果,Default 方法直接返回对应的匹配结果,否则就返回默认值。
iOS LLVM 中的宏定义的更多相关文章
- iOS 静态库中使用宏定义区分iPhone模拟器与真机---备用
问题描述 一般项目中,可以使用宏定义来判断模拟器还是真机,这无疑是有效的. #if TARGET_IPHONE_SIMULATOR #define SIMULATOR 1 #elif TARGET_O ...
- iOS开发经常使用宏定义
iOS开发经常使用宏定义 iOS开发中经常须要获取屏幕宽度高度,为view设置颜色,为imgagView设置图片等,我们都可定义一些宏,随时都可拿来使用,方便开发 <span style=&qu ...
- C++中的内联函数和C中的宏定义的区别
在C++中内联函数: 内联函数即是在函数的声明和和定义前面加上“inline”关键字,内联函数和常规函数一样,都是按照值来传递参数的,如果参数为表达式,如4.5+7.5,则函数将传递表达式的值(这里为 ...
- Makefile中进行宏定义-***
实际上是gcc命令支持-D宏定义,相当于C中的全局#define: gcc -D name gcc -D name=definition Makefile中可以定义变量(和宏很像),但是是给make解 ...
- C语言学习笔记--C语言中的宏定义
1. C 语言中的宏定义 (1)#define 是预处理器处理的单元实体之一(因此,预处理器只是简单的进行替换,并不(2)#define 定义的宏可以出现在程序的任意位置(包括函数体的内部)(3)#d ...
- Makefile中用宏定义进行条件编译(gcc -D)/在Makefile中进行宏定义-D【转】
本文转载自:http://blog.csdn.net/maopig/article/details/7230311 在源代码里面如果这样是定义的:#ifdef MACRONAME//可选代码#en ...
- 内核中的宏定义__init、__initdata和__exit、__exitdata
__init.__initdata和__exit.__exitdata的定义位于<kernel/include/linux/init.h> /* These are for everybo ...
- [C++] C++中的宏定义详解
转载自:C++中的宏定义 和 C++宏定义详解 一.#define解析 #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率 ...
- iOS 7:漫谈#define 宏定义(转)
iOS :漫谈#define 宏定义 #define宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步 ...
- iOS开发常见的宏定义(实用)
iOS开发过程中使用一些常用的宏可以提高开发效率,提高代码的重用性:将这些宏放到一个头文件里然后再放到工程中的-Prefix.pch文件中(或者直接放到-Prefix.pch中)直接可以使用,灰常方便 ...
随机推荐
- KingbaseES Create Index Concurrently 过程探究
前言: 我们知道Oracle 可以通过create index online 在线创建索引,而不影响其他会话修改数据,但Oracle 实际在online 创建索引的最后一步,实际还是需要进行锁升级,申 ...
- Rust 实现日志记录功能
目录 log 日志库标准 简单示例 使用方法 库的开发者 应用开发者 日志库开发者 使用 log4rs 添加依赖 配置文件 运行项目 参考文章 log 日志库标准 log 是 Rust 的日志门面库, ...
- S锁,X锁,乐观锁和悲观锁
S锁:S锁也叫共享锁,读锁,数据只能被读取不能被修改. X锁:X锁也叫排他锁,写锁,一个事务对表加锁后,其他事务就不能对其进行加锁与增删查改操作. 乐观锁:总是假设是最好的情况,每次去操作的时候都不会 ...
- Python企业面试题1 —— 基础篇
1. b.B.KB.MB.GB的关系? b ---- 位(bit) B ---- 字节(一个字节等于8位) 1 B = 8 bit 1 KB = 1024 B 1 MB = 1024 KB 1 GB ...
- 对OpenHarmony中LiteOS的内核分析——超时原理和应用
前言 在软件世界里面,超时是一个非常重要的概念.比如 ● 当前线程暂时休眠1秒钟,休眠结束后继续执行 ● 每5秒钟采集一下CPU利用率 ● 数据发送失败,2秒钟以后再试一试 ● 等待某种数据,但最多等 ...
- 深入理解 SQL UNION 运算符及其应用场景
SQL UNION运算符 SQL UNION运算符用于组合两个或多个SELECT语句的结果集. 每个UNION中的SELECT语句必须具有相同数量的列. 列的数据类型也必须相似. 每个SELECT语句 ...
- 深度解析 Spring Security:身份验证、授权、OAuth2 和 JWT 身份验证的完整指南
Spring 安全框架 Spring Security 是一个用于保护基于 Java 的应用程序的框架.它是一个功能强大且高度可定制的身份验证和访问控制框架,可以轻松地集成到各种应用程序中,包括 We ...
- C#只允许启动一个WinFrom进程
[STAThread] public static void Main() { bool ret; System.Threading.Mutex mutex = new System.Thread ...
- WPF入门-导航页
本文适合有winform或web前端基础,且有C#开发基础的同学学习. 本文介绍的开发模式属于MVVM,即Model-View-ViewModel .这种开发模式上手简单,开发迅速. wpf入门第一篇 ...
- CMakeLists.txt 编写模板
新建文件 CMakeLists.txt # 设置cmake的最低版本 cmake_minimum_required(VERSION 2.8) # 指定为C++11 版本 set(CMAKE_CXX ...