Clang Module 内部实现原理及源码分析

简介: 钉钉工程开始支持Swift,在适配clang module的过程中,遇到了各种各样的编译问题,为了弄清楚这些编译失败的真正原因,以及clang module的最佳实践,决定通过深入阅读clang module的实现代码,来解开这些谜团。

编译参数

Xcode的Build Settings针对Clang Module有专门的设置分组,如下图:

image.png

针对这几个设置参数,下面分别解释一下其作用。

Enable Modules (C and Objective-C)

是否开启Clang Module特性。

当设置为YES的时候,会设置编译器参数-fmodules,开启clang module特性。当设置为NO的时候,其它4个选项也会随之失效,不会设置编译器参数-fmodules。

Enable Clang Module Debugging

对引用的外部clang module或者预编译头文件生成调试信息

当设置为YES的时候,会设置编译器参数-gmodules

举例说明一下这个参数,我们自己模块的Objective-C源代码中如果有#import <Foundation/Foundation.h>,那Foundation模块就属于被引用的外部clang module。当开启Clang Module特性的时候,会根据Foundation模块提供的modulemap生成clang module编译缓存,其缓存的目录是通过编译器参数-fmodules-cache-path来设定的。

默认Xcode会设定编译缓存目录为的ModuleCache.noindex

-fmodules-cache-path=/Users/baozhifei/Library/Developer/Xcode/DerivedData/ModuleCache.noindex

如下图红框中所示,ModuleCache.noindex为clang module缓存目录,Foundation-3DFYNEBRQSXST.pcm为Foundation的缓存文件

image.png

当Enable Clang Module Debugging为YES的时候,这个缓存文件为Mach-O格式的文件,其中__CLANG,__clangast节为缓存内容,这个文件还携带__DWARF,__debug_info等一些调试信息。

其中缓存内容的头4个字节签名是CPCH,应该是Compiled PCH的缩写。

image.png

当Enable Clang Module Debugging为NO的时候,缓存文件直接就是CPCH文件,不会生成Mach-O格式且携带调试信息。

image.png

建议正常开发的时候关闭这个设置,当出现clang module编译问题的时候,可以开启这个调试选项,有了DWARF的调试信息,可以精确定位的错误代码的行号和列号。

开启这个选项后,编译时会有性能损失,因为缓存变成了Mach-O格式,需要完整加载整个文件,读取__clangast节,才能获取真正的缓存内容。

Apple的官方文档《Precompiled Header and Modules Internals》中原文描述如下:

Clang’s AST files are loaded “lazily” from disk. When an AST file is initially loaded, Clang reads only a small amount of data from the AST file to establish where certain important data structures are stored. The amount of data read in this initial load is independent of the size of the AST file, such that a larger AST file does not lead to longer AST load times.

从下面代码中,就可以看出,CPCH文件内容其实就是AST的bitcode,所以,clang module的实现机制是和预编译头文件一致的,clang module可以认为是更通用的预编译头文件。

640.png

Disable Private Modules Warnings

对于Private Module概念还不了解,后面再展开

Allow Non-modular Includes In Framework Modules

允许framework模块中有非clang module的include

当设置为NO的时候,会设置编译器参数-Wnon-modular-include-in-framework-module。如果在引用的模块中,遇到非clang module的头文件,例如 #import "XXX.h" 这样格式的import指令,就会报错。

Link Frameworks Automatically

对于开启clang module后,import clang module会自动对链接器ld64增加链接参数,如下图红框所示:

image.png

因为我们都是使用CocoaPod来管理依赖,所以,最好关闭此选项,统一在podspec中声明依赖的frameworks和weak_frameworks

关闭后,会增加编译器参数-fno-autolink

Defines Module

image.png

-fmodule-name=ClangModuleTest

一个是形成一个虚拟的clang module层,让我们当前源码编译的模块也可以伪装成clang module格式

-ivfsoverlay /Users/baozhifei/Library/Developer/Xcode/DerivedData/ClangModuleTest-frvkjzzwryjshkeuimnrjtpuowxm/Build/Intermediates.noindex/ClangModuleTest.build/Debug-iphonesimulator/ClangModuleTest.build/all-product-headers.yaml

这个文件格式有点问题,说是yaml格式,实际上内容是json

image.png

源代码

llvm项目地址:https://github.com/llvm/llvm-project

我们编译调试的版本是14.0.6,是目前最新的tag

调试的代码,用Xcode创建一个新的framework工程叫ClangModuleTest,新增Test类,我们分析Test.m的编译流程。


image.png

image.png

Xcode内置的clang版本应该是有一些功能没有开源,我们开源的clang不认识-index-unit-output-path-index-store-path,调试的时候这两个参数删除即可。

最新版本的clang的编译参数,都统一定义在Options.td文件中,通过clang-tblgen来统一生成,这样生成出来的rst文档和Options.inc是一致的,在Options.td中没有找到上述两个参数。

我这里调试的环境是Qt Creator,设置运行参数如下图

image.png

如果只调试不写代码,可以使用Xcode来调试,如果还要编译代码,最好使用vscode,clion,qt creator这种支持cmake的IDE,可以使用ninja来构建,编译会非常快。Qt Creator界面丑一点,但是稳定,cmake自带一个GUI配置界面,很方便。

image.png

如果是vscode,要配置settings.json和launch.json文件,内容大致如下:

image.png

预处理

clang::Preprocessor是负责预处理的类,预处理主要是处理编译单元中的一些#号开头的预处理指令,比如,#import导入头文件预编译指令,这些指令定义在TokenKinds.def文件中。

640 (1).png

image.png

image.png

image.png

image.png

image.pngimage.png

image.png

clang会开启另外一个线程来编译Foundation模块,并把编译结果写入到pcm文件中。这个CompilerInstance执行的FrontendAction就是GenerateModuleFromModuleMapAction,我们之前编译.o文件实际上是EmitObjAction。


image.png

image.png

image.png


相关文章
|
C语言 索引
09-iOS之load和initialize底层调用原理分析
09-iOS之load和initialize底层调用原理分析
92 0
|
1月前
|
Java
Optional源码分析(涉及Objects源码和Stream源码)
本文分析了Java中Optional类的源码,包括其内部的Objects.requireNonNull方法、EMPTY定义、构造方法、ofNullable方法、isEmpty方法以及如何与Stream类交互,展示了Optional类如何避免空指针异常并提供流式操作。
36 0
Optional源码分析(涉及Objects源码和Stream源码)
|
3月前
|
JavaScript 前端开发 开发者
深入解析Angular装饰器:揭秘框架核心机制与应用——从基础用法到内部原理的全面教程
【8月更文挑战第31天】本文深入解析了Angular框架中的装饰器特性,包括其基本概念、使用方法及内部机制。装饰器作为TypeScript的关键特性,在Angular中用于定义组件、服务等。通过具体示例介绍了`@Component`和`@Injectable`装饰器的应用,展示了如何利用装饰器优化代码结构与依赖注入,帮助开发者构建高效、可维护的应用。
34 0
|
3月前
|
Java 编译器 开发者
JDK8到JDK23版本升级的新特性问题之编写一个简单的module-info.java文件,如何实现
JDK8到JDK23版本升级的新特性问题之编写一个简单的module-info.java文件,如何实现
|
4月前
|
NoSQL Redis C++
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
|
6月前
|
Java Linux 调度
Java【付诸实践 03】Spring定时任务注解@Scheduled+@EnableAsync用法详解(简单说明+应用场景+demo源代码+执行过程分析)
Java【付诸实践 03】Spring定时任务注解@Scheduled+@EnableAsync用法详解(简单说明+应用场景+demo源代码+执行过程分析)
125 2
|
存储 JSON 缓存
重学Node系列01-模块规范及模块加载机制
Node模块规范及模块加载机制 这是重新阅读《深入浅出NodeJS》的相关笔记,这次阅读发现自己依旧收获很多,而第一次阅读的东西也差不多忘记完了,所以想着这次过一遍脑子,用自己的理解输出一下,方便记忆以及以后回忆... 历史原因,JavaScript以前是没有模块机制的,这对于node来说想要编写一个大型项目是很难的,所以node采用了社区提出的CommmonJS规范 认识CommonJS 这里主要介绍的是大家常见的JavaScript文件模块,其他的将在后续章节介绍
86 0
|
JSON 自然语言处理 JavaScript
ES5是什么意思?ES6是什么意思?它们的区别是什么?底层原理是什么?
ES5是什么意思?ES6是什么意思?它们的区别是什么?底层原理是什么?
361 0
go语言module,依赖管理方法
1.为什么需要依赖管理 最早的时候,Go所依赖的所有的第三方库都放在GOPATH这个目录下面。这就导致了同一个库只能保存一个版本的代码。如果不同的项目依赖同一个第三方的库的不同版本,应该怎么解决? go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具
185 0
go语言module,依赖管理方法
怎么实现vuex源码的核心类ModuleCollection
怎么实现vuex源码的核心类ModuleCollection
84 0