llvm 对 copy 属性的优化(2)
前言
本文将会回答两个问题:
- 什么场景会调用
objc_copyCppObjectAtomic
函数? 在上篇文章中,我们并没有发现任何场景会调用objc_copyCppObjectAtomic
函数。 - copyHelper 是如何生成的? iOS 中 copy 的原理中曾经提到作者没有找到 copyHelper(dest, src); 的实现方法。
含 c++ 类的复制行为
为了解释前言中的两个问题,我们需要在 CopyMock
新增了一个属性 str
,该属性的类型是 std::string
。
#include <string>
@interface CopyMock : NSObject
@property (readwrite) std::string str;
@end
与第一篇文章类似,我们先将代码编译为中间码。如下所示:
// 赋值函数
; Function Attrs: noinline optnone ssp uwtable
define internal void @" 1-[CopyMock setStr:]"(%0*, i8*, %"class.std::__1::basic_string"*) #0 !dbg !1897 {
%4 = alloca %0*, align 8
%5 = alloca i8*, align 8
store %0* %0, %0** %4, align 8
call void @llvm.dbg.declare(metadata %0** %4, metadata !1900, metadata !DIExpression()), !dbg !1901
store i8* %1, i8** %5, align 8
call void @llvm.dbg.declare(metadata i8** %5, metadata !1902, metadata !DIExpression()), !dbg !1901
call void @llvm.dbg.declare(metadata %"class.std::__1::basic_string"* %2, metadata !1903, metadata !DIExpression()), !dbg !1901
%6 = load %0*, %0** %4, align 8, !dbg !1904
%7 = bitcast %0* %6 to i8*, !dbg !1904
%8 = getelementptr inbounds i8, i8* %7, i64 32, !dbg !1904
%9 = bitcast i8* %8 to %"class.std::__1::basic_string"*, !dbg !1904
%10 = bitcast %"class.std::__1::basic_string"* %9 to i8*, !dbg !1904
%11 = bitcast %"class.std::__1::basic_string"* %2 to i8*, !dbg !1904
call void @objc_copyCppObjectAtomic(i8* %10, i8* %11, i8* bitcast (void (%"class.std::__1::basic_string"*, %"class.std::__1::basic_string"*)* @__assign_helper_atomic_property_ to i8*)), !dbg !1904
ret void, !dbg !1904
}
编译器会先帮开发者增加一个普通的赋值方法 -[CopyMock setStr:]
。
该方法最后会调用 objc_copyCppObjectAtomic
函数,
三个参数分别是:
- 属性str的存储地址,CopyMock 实例的偏移 32 位
`%8 = getelementptr inbounds i8, i8* %7, i64 32, !dbg !1904`
- 被复制
str
的地址`%11 = bitcast %"class.std::__1::basic_string"* %2 to i8*, !dbg !1904`
- 由编译器生成的辅助复制函数
`__assign_helper_atomic_property_`
__assign_helper_atomic_property_
在分析 __assign_helper_atomic_property_
的内部逻辑前,我们需要再看一遍 objc_copyCppObjectAtomic
函数的声明: void objc_copyCppObjectAtomic(void *dest, const void *src, void (*copyHelper) (void *dest, const void *source))
。
通过原型我们可以发现 copyHelper
是 objc_copyCppObjectAtomic
的第三个参数。
实际上,void (*copyHelper) (void *dest, const void *source))
是一个函数指针,它的实现是通过调用方传参决定。在本例中,它会指向 __assign_helper_atomic_property_
。
在阅读 __assign_helper_atomic_property_
的代码前,需要先准备几个小知识:
- c++ 的函数名会被 name mangle,所以
_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSERKS5_
实际上代表字符串复制函数std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::operator=(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
- 中间码具有 ssp 特征,阅读比较繁琐,编译后的汇编会被高度优化,下一章会讲到编译器的优化
- c++ 中的
std::string
和class.std::__1::basic_string
是等价的 - alloca 可以在栈中开辟空间,具有效率高,不需要主动释放等特性
小知识准备结束,下面开始对 __assign_helper_atomic_property_
的实现内容进行分析:
// 辅助赋值函数
; Function Attrs: noinline ssp uwtable
define internal void @__assign_helper_atomic_property_(%"class.std::__1::basic_string"*, %"class.std::__1::basic_string"*) #5 !dbg !1891 {
// 在栈中开辟空间
%3 = alloca %"class.std::__1::basic_string"*, align 8
// 在栈中开辟空间,备用
%4 = alloca %"class.std::__1::basic_string"*, align 8
// 存储参数 %0 到 %3
store %"class.std::__1::basic_string"* %0, %"class.std::__1::basic_string"** %3, align 8
call void @llvm.dbg.declare(metadata %"class.std::__1::basic_string"** %3, metadata !1892, metadata !DIExpression()), !dbg !1893
// 存储参数 %1 到 %4
store %"class.std::__1::basic_string"* %1, %"class.std::__1::basic_string"** %4, align 8
call void @llvm.dbg.declare(metadata %"class.std::__1::basic_string"** %4, metadata !1894, metadata !DIExpression()), !dbg !1893
// 取出参数 %4 到 %5
%5 = load %"class.std::__1::basic_string"*, %"class.std::__1::basic_string"** %4, align 8, !dbg !1893
// 取出参数 %3 到 %6
%6 = load %"class.std::__1::basic_string"*, %"class.std::__1::basic_string"** %3, align 8, !dbg !1893
// 调用字符串复制方法 std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::operator=(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
%7 = call dereferenceable(24) %"class.std::__1::basic_string"* @_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSERKS5_(%"class.std::__1::basic_string"* %6, %"class.std::__1::basic_string"* dereferenceable(24) %5), !dbg !1893
ret void, !dbg !1895
}
这个函数的逻辑很简单,相当于对 字符串复制函数 std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::operator=(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
进行了一次间接调用。
总结
本文通过将代码改造为 cpp 代码,可以得到以下两个信息:
- copyHelper 通常是由编译器生成的辅助函数
- 具有 cpp 相关类复制的场景才会触发对
objc_copyCppObjectAtomic
函数的调用
- hdu------(3549)Flow Problem(最大流(水体))
- go语言实现http服务端与客户端
- hdu-----(1532)Drainage Ditches(最大流问题)
- LNMP无法删除.user.ini文件的解决方法
- HDU-----(4858)项目管理(模拟)
- hdu-----(4857)逃生(拓扑排序)
- HDU-----(1083)Courses(最大匹配)
- HDU----(3294)Girls' research(manacher)
- map
- hdu----(3068)最长回文(manacher)
- hdu---(1280)前m大的数(计数排序)
- 程序员你为什么这么累【续】:编码习惯之Controller规范
- go-nsq使用简述
- hdu---(4515)小Q系列故事——世界上最遥远的距离(模拟题)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Angular页面调试一个有用的小技巧 - normalizeDebugBindingName和normalizeDebugBindingValue
- 19个JS超有用的简写技巧
- 【教程】移植web server到Ubuntu就是这么简单!
- 为什么采用Proxy重构响应系统 | Vue3源码系列
- 制作Linux嵌入式系统开机LOGO(图片)
- R语言奇淫巧技之pdftools包
- 14个Spring MVC超实用技巧!
- 想要成为前端Star 吗?一首歌时间将React / Vue 应用Docker 化
- 初识Pandas
- 【教程】从零制作文件系统到jz2440,使其支持telnet , ftp 和tftp
- Mybatis源码学习第六天(核心流程分析)之Executor分析
- EasyPoi导出Excel
- 盘点 15 个好用的 API 接口管理神器
- JVM学习第二天(垃圾回收器和内存分配策略)大章
- JVM学习第一天(虚拟机的前世今生与与Java的内存区域)