前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >llvm 对 copy 属性的优化(2)

llvm 对 copy 属性的优化(2)

作者头像
酷酷的哀殿
发布2020-10-26 15:21:09
4240
发布2020-10-26 15:21:09
举报
文章被收录于专栏:酷酷的哀殿

前言

本文将会回答两个问题:

  • 什么场景会调用 objc_copyCppObjectAtomic 函数? 在上篇文章中,我们并没有发现任何场景会调用 objc_copyCppObjectAtomic 函数。
  • copyHelper 是如何生成的? iOS 中 copy 的原理中曾经提到作者没有找到 copyHelper(dest, src); 的实现方法。

含 c++ 类的复制行为

为了解释前言中的两个问题,我们需要在 CopyMock 新增了一个属性 str,该属性的类型是 std::string

代码语言:javascript
复制
#include <string>

@interface CopyMock : NSObject

@property (readwrite) std::string str;

@end

与第一篇文章类似,我们先将代码编译为中间码。如下所示:

代码语言:javascript
复制
// 赋值函数
; Function Attrs: noinline optnone ssp uwtable
define internal void @"\01-[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))

通过原型我们可以发现 copyHelperobjc_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::stringclass.std::__1::basic_string 是等价的
  • alloca 可以在栈中开辟空间,具有效率高,不需要主动释放等特性

小知识准备结束,下面开始对 __assign_helper_atomic_property_ 的实现内容进行分析:

代码语言:javascript
复制
// 辅助赋值函数
; 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 函数的调用
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 酷酷的哀殿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 含 c++ 类的复制行为
  • __assign_helper_atomic_property_
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档