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


相关文章
|
5月前
|
前端开发
【面试题】如何使用ES6 ... 让代码优雅一点?
【面试题】如何使用ES6 ... 让代码优雅一点?
|
1月前
|
JavaScript 前端开发 开发者
探索yocto-queue库:替代数组的实现原理与方法
在需要高性能队列结构的场景下,yocto-queue提供了一个轻量级且高效的解决方案。它的实现原理优雅且有效,使得在实际应用中,特别是在性能敏感的环境下,成为了数组的一个强大替代者。通过减少性能开销,yocto-queue使得JavaScript开发者能够构建更快、更可靠的应用程序,从而提高用户体验和应用性能。
39 2
|
2月前
|
JavaScript 前端开发 开发者
深入解析Angular装饰器:揭秘框架核心机制与应用——从基础用法到内部原理的全面教程
【8月更文挑战第31天】本文深入解析了Angular框架中的装饰器特性,包括其基本概念、使用方法及内部机制。装饰器作为TypeScript的关键特性,在Angular中用于定义组件、服务等。通过具体示例介绍了`@Component`和`@Injectable`装饰器的应用,展示了如何利用装饰器优化代码结构与依赖注入,帮助开发者构建高效、可维护的应用。
31 0
|
2月前
|
Java 编译器 开发者
JDK8到JDK23版本升级的新特性问题之编写一个简单的module-info.java文件,如何实现
JDK8到JDK23版本升级的新特性问题之编写一个简单的module-info.java文件,如何实现
|
5月前
|
Java Linux 调度
Java【付诸实践 03】Spring定时任务注解@Scheduled+@EnableAsync用法详解(简单说明+应用场景+demo源代码+执行过程分析)
Java【付诸实践 03】Spring定时任务注解@Scheduled+@EnableAsync用法详解(简单说明+应用场景+demo源代码+执行过程分析)
113 2
|
5月前
|
JSON 缓存 JavaScript
Go语言依赖管理的核心 - go.mod文件解析
Go语言依赖管理的核心 - go.mod文件解析
279 0
|
11月前
|
JavaScript
ES6的基础用法
对js es6的用法进行简单介绍
es6如何使用padStart()和padEnd()方法
es6如何使用padStart()和padEnd()方法
86 0
|
索引
从underscore源码看如何实现map函数
经常会看到这样的面试题,让面试者手动实现一个 map 函数之类的,嗯,貌似并没有什么实际意义。但是对于知识探索的步伐不能停止,现在就来分析下如何实现 map 函数。
92 0
从underscore源码看如何实现map函数
|
JSON 自然语言处理 JavaScript
ES5是什么意思?ES6是什么意思?它们的区别是什么?底层原理是什么?
ES5是什么意思?ES6是什么意思?它们的区别是什么?底层原理是什么?
347 0