LLVM AliasAnalysis别名分析 TBAA TypeBasedAliasAnalysis

一、什么是别名分析

Alias Analysis (又名 Pointer Analysis)是用于确定两个指针是否指向内存中的同一对象,这里有很多不同的别名分析算法,分为几种类型:流敏感vs流非敏感、上下文敏感vs上下文非敏感、域敏感vs域非敏感、基于一致性的vs基于子集的。传统的别名分析用于回答must、may、no的问题,也即两个指针总是指向同一对象,可能指向同一对象以及绝不会指向同一对象。(SSA—静态单一赋值,将同一变量名用多个名表示,被赋值的变量名不会重复,便于寻找变量的产生与使用点)。 

二、别名分析有什么用

有这样一个例子:

int foo (int __attribute__((address_space(0)))* a,
         int __attribute__((address_space(1)))* b) {
    *a = 42;
    *b = 20;
    return *a;
}

__attribute__ 指示符指定 a 是一个i32*的地址,b 是一个i32 addrspace(1)*的地址,生成ir如下:

define i32 @foo(i32* nocapture %0, i32 addrspace(1)* nocapture %1) local_unnamed_addr #0 {
  store i32 42, i32* %0, align 4, !tbaa !10
  store i32 20, i32 addrspace(1)* %1, align 4, !tbaa !10
  %3 = load i32, i32* %0, align 4, !tbaa !10
  ret i32 %3
}

我们先忽略指令后边的!tbaa这个metadata信息,这个之后会讲到。这个是clang在O2的场景下生成的IR。现在需要对于函数foo进行进一步的优化,去掉不必要的load:

define i32 @foo(i32* nocapture %0, i32 addrspace(1)* nocapture %1) local_unnamed_addr #0 {
  store i32 42, i32* %0, align 4, !tbaa !10
  store i32 20, i32 addrspace(1)* %1, align 4, !tbaa !10
  ret i32 42
}

但是这个优化有一个前提,就是a和b不能别名,不然可能会有错误:

int i = 0;
int result = foo(&i, &i);

以上可以看到,以上调用会使a和b别名,本应该返回20,结果因为优化的缘故返回了42,导致错误。所以编译器只有确定两个指针不会产生别名时,才能进行以上优化。这个就是别名分析的作用的一个小例子。

三、当前LLVM内置的别名分析

当前LLVM内置使用了多种别名分析,我们这里简单介绍下。

3.1 -basic-aa

  • 不同全局变量、栈分配和堆分配之间永远不会发生别名冲突。 这意味着,如果两个指针分别指向全局变量、栈分配和堆分配,那么它们一定指向不同的内存位置。
  • 全局变量、栈分配和堆分配永远不会与空指针发生别名冲突。 这意味着,指向全局变量、栈分配和堆分配的指针一定指向有效的内存地址,而不是空指针。
  • 结构体的不同成员之间不会发生别名冲突。 也就是说,指向结构体不同成员的指针一定指向不同的内存位置。
  • 具有静态不同的下标的数组索引不会发生别名冲突。 例如,a[0] 和 a[1] 指向不同的内存位置,因此它们不会发生别名冲突。
  • 许多常见的标准 C 库函数不会访问内存或者只读内存。 这意味着,如果一个指针指向由这些函数返回的内存位置,那么它不会与其他指针发生别名冲突。
  • 显然指向常量全局变量的指针具有 "pointToConstantMemory" 属性。 这意味着,指向常量全局变量的指针永远不会被修改,因此不会发生别名冲突。
  • 如果函数调用从未将栈分配的变量传递到函数外部,那么该函数调用不会修改或引用这些变量。 这意味着,在函数内部创建的自动数组不会与其他指针发生别名冲突。

3.2 -typebased-aa(tbaa)

这个就是我们在上边的那个IR中看到的metadata信息,这里是基于类型的别名分析。我们先看一个简单的C++的例子。

#include <cstdio>
using namespace std;

struct tests {
  int size;
  int *arr;
  tests(int _size, int *_arr) : size(_size), arr(_arr) {}

  __attribute__((noinline)) void dump() {
    for (int i = 0; i < size; ++i) {
      printf("%d ", arr[i]);
    }
    printf("\n");
  }
};

int main() {
  int arr1[5] = { 1, 2, 3, 4, 5 };
  int arr2[3] = { 6, 7, 8 };
  tests t(5, arr1);
  t.dump();
  printf("\n");
  return 0;
}

生成的IR如下所示,这里我截取了关键部分的IR:

; i32:size i32*:arr
%struct.tests = type { i32, i32* }

define i32 @main() local_unnamed_addr #0 {
  %1 = alloca [5 x i32], align 4
  %2 = alloca %struct.tests, align 8
  %3 = bitcast [5 x i32]* %1 to i8*
  call void @llvm.lifetime.start.p0i8(i64 20, i8* nonnull %3) #6
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* noundef nonnull align 4 dereferenceable(20) %3, i8* noundef nonnull align 4 dereferenceable(20) bitcast ([5 x i32]* @__const.main.arr1 to i8*), i64 20, i1 false)
  %4 = bitcast %struct.tests* %2 to i8*
  call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %4) #6
  %5 = getelementptr inbounds [5 x i32], [5 x i32]* %1, i64 0, i64 0
  %6 = getelementptr inbounds %struct.tests, %struct.tests* %2, i64 0, i32 0
  ; 存size到struct中
  store i32 5, i32* %6, align 8, !tbaa !10
  %7 = getelementptr inbounds %struct.tests, %struct.tests* %2, i64 0, i32 1
  ; 存arr到struct中
  store i32* %5, i32** %7, align 8, !tbaa !16
  call void @_ZN5tests4dumpEv(%struct.tests* nonnull %2)
  %8 = call i32 @putchar(i32 10)
  call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %4) #6
  call void @llvm.lifetime.end.p0i8(i64 20, i8* nonnull %3) #6
  ret i32 0
}

!10 = !{!11, !12, i64 0}
!11 = !{!"_ZTS5tests", !12, i64 0, !15, i64 8}
!12 = !{!"int", !13, i64 0}
!13 = !{!"omnipotent char", !14, i64 0}
!14 = !{!"Simple C++ TBAA"}
!15 = !{!"any pointer", !13, i64 0}
!16 = !{!11, !15, i64 8}
!17 = !{!12, !12, i64 0}

我们先看一下tbaa的结构,tbaa存的主要是类型信息,概括一下就是三个数据,我们当前处理的base的类型是什么,我们当前获取到的实际类型是什么,这个实际类型在base中的偏移是多少。我们可以看存arr的这条store指令是16这条metadata,其中11就是base类型也就是test,11中存了test类型的layout,然后16中的15就是获取到的类型,对于store指令也就是%5的类型是个指针,也就与15的类型相匹配,最终的这些类型都会指向13和14这两条公共父节点,这样其实构成了一个树状结构。然后16中的第三个操作数是8表示arr在test偏移为8的位置,同时我们在树上找test这个节点能看到偏移8对应的是15这条,也与16的实际类型相一致。这个就是tbaa的一个例子,其实就是构成了一个类型树。

基于类型我们能够判断出来一些别名关系,例如:

  • 如果a和b具有公共的子类型,但是a和b相互之间都不是对方的sub type,那么a和b是noalias的。
  • 判断两个类型是否具有重叠关系,不具有的话那肯定是noalias的;如果具有重叠关系的话再根据offset判断是否是处理的相同memory处的内容
  • 等等

3.3 -globalsmodres-aa

此pass针对未“占用地址”的internal的全局变量实现了简单的上下文敏感的 mod/ref 和别名分析。如果全局变量的地址未被占用,则此过程知道没有指针为全局变量的别名。此过程还会跟踪它知道永远不会访问内存或永远不会读取内存的函数。这允许某些优化(例如 GVN)完全消除调用指令。

此过程的真正强大之处在于它为调用指令提供了上下文敏感的 mod/ref 信息。这允许优化器知道对函数的调用不会破坏或读取全局变量的值,从而可以消除加载和存储。

此过程的范围有些有限(仅支持非地址占用的全局变量),但分析速度非常快。

3.4 -steens-aa

-steens-aa 过程实现了著名的“Steensgaard 算法”的变体,用于过程间别名分析。Steensgaard 算法是一种基于统一、不区分流、不区分上下文和不区分字段的别名分析,并且可扩展性极强(有效线性时间)。

LLVM -steens-aa 过程使用数据结构分析框架实现了 Steensgaard 算法的“推测性字段敏感”版本。这使其比标准算法具有更高的精度,同时保持了出色的分析可扩展性。

-steens-aa 在可选的“poolalloc”模块中可用。它不是 LLVM 核心的一部分。

3.5 -ds-aa

-ds-aa 实现了完整的数据结构分析算法。数据结构分析是一种基于模块化统一、不区分流、上下文敏感和推测字段敏感的别名分析,并且可扩展性也相当高,通常为 O(n * log(n))。

此算法能够响应各种别名分析查询,并且还可以提供上下文敏感的 mod/ref 信息。迄今为止尚未实现的唯一主要功能是对必须别名信息的支持。

-ds-aa在可选的“poolalloc”模块中可用。它不是 LLVM 核心的一部分。

3.6 -scev-aa

-scev-aa 通过将 AliasAnalysis 查询转换为 ScalarEvolution 查询来实现 AliasAnalysis 查询。与其他别名分析相比,这使其对 getelementptr 指令和循环感应变量的理解更加完整。

四、依赖于别名分析的几个优化

4.1 -adce

-adce这个pass全称是Aggressive Dead Code Elimination,是用于死代码消除的一个pass, 过程使用 AliasAnalysis 接口删除那些没有副作用且未使用的函数调用。

4.2 -licm

-licm这个pass全称是Loop Invariant Code Motion,实现了各种循环不变代码外提的相关优化。它使用 AliasAnalysis 接口进行几种不同的转换:

  • 如果循环中没有修改已加载内存的指令,则使可以将加载指令提出循环。
  • 它将不写入内存且循环不变的函数调用提出循环。
  • 它使用别名信息将加载并存储在循环中的内存对象提升到寄存器中。如果已加载/存储的内存位置没有别名,则可以执行此操作。

4.3 -argpromotion

-argpromotion 过程将按引用传递的参数提升为按值传递。特别是,如果仅从中加载指针参数,则将加载的值而不是地址传递给函数。此过程使用别名信息来确保从参数指针加载的值在函数入口和任何指针加载之间不会被修改。

4.4 -gvn,-memcpyopt,-dse

这些pass使用 AliasAnalysis 信息来分析store和load相关的指令。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/757941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

后端之路第三站(Mybatis)——入门配置

一、Mybatis是啥&#xff1f; 就是一个用java来操控数据库的框架语言 之前学的datagrip或者navicat这些软件里我们操作数据库&#xff0c;原理是我们编写完的操作语句发送到服务器传送到数据库系统&#xff0c;然后数据库执行完之后再发送给服务器返回给datagrip或者navicat显…

独立开发者系列(13)——示例理解面向对象与过程

专业术语晦涩难懂&#xff0c;特别是当你没有写过稍微大点的系统的时候&#xff0c;你要理解这里面的区别很难。 从最简单的早期我们学习开始&#xff0c;我们除了练习hello world掌握了入门函数之后&#xff0c;基本都再练习算法。比如水仙花数的获取&#xff0c;冒泡排序&…

phpMyAdmin | mysqli::real_connect(): (HY000/2002): No such file or directory

法一&#xff1a;第一次安装宝塔 第一次安装宝塔mysql服务是默认关闭的&#xff0c;需要手动打开&#xff0c;打开服务再次进入phpMyAdmin发现可以进入了 法二&#xff1a;第一种方法没解决用这种 出现mysqli::real_connect(): (HY000/2002): No such file or directory错误通…

Java | Leetcode Java题解之第206题反转链表

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode reverseList(ListNode head) {if (head null || head.next null) {return head;}ListNode newHead reverseList(head.next);head.next.next head;head.next null;return newHead;} }

ROS2自定义接口Python实现机器人移动

1.创建机器人节点接口 cd chapt3_ws/ ros2 pkg create example_interfaces_rclpy --build-type ament_python --dependencies rclpy example_ros2_interfaces --destination-directory src --node-name example_interfaces_robot_02 --maintainer-name "Joe Chen" …

20240630 每日AI必读资讯

&#x1f4da;全美TOP 5机器学习博士发帖吐槽&#xff1a;实验室H100数量为0&#xff01; - 普林斯顿、哈佛「GPU豪门」&#xff0c;手上的H100至少三四百块&#xff0c;然而绝大多数ML博士一块H100都用不上 - 年轻的研究者们纷纷自曝自己所在学校或公司的GPU情况&#xff1a…

vue开发网站--关于window.print()调取打印

1.vue点击按钮调取打印 点击按钮&#xff1a; 调取打印该页面&#xff1a; <div click"clickDown()">下载</div>methods: {//下载-调取打印clickDown() {window.print()}, }<style>/* 点击打印的样式 */media print {.clickDown {display: no…

【测试】软件测试规程(word原件)

软件测试规程的作用在于确保软件测试活动的系统性、规范性和一致性。它明确了测试的目标、范围、方法、流程以及所需资源&#xff0c;为测试人员提供了明确的指导和操作规范。通过遵循测试规程&#xff0c;可以提高测试效率&#xff0c;减少测试遗漏和错误&#xff0c;保证软件…

K8S基础简介

用于自动部署&#xff0c;扩展和管理容器化应用程序的开源系统。 功能&#xff1a; 服务发现和负载均衡&#xff1b; 存储编排&#xff1b; 自动部署和回滚&#xff1b; 自动二进制打包&#xff1b; 自我修复&#xff1b; 密钥与配置管理&#xff1b; 1. K8S组件 主从方式架…

神经网络原理

神经网络原理是一种模拟人脑的机器学习技术&#xff0c;通过大量的神经元和层次化的连接进行信息处理和学习。 图1 神经元 神经网络由许多简单的计算单元或“神经元”组成&#xff0c;这些神经元通过连接传递信息。每个连接都有一个权重&#xff0c;用于调整传递的信号强度。这…

【Red Hat】 Red Hat 系统提示未注册订阅管理,并进行阿里yum源更新的解决方案

目录 &#x1f30a;1. 问题说明 &#x1f30a;2. 解决方案 &#x1f30d;2.1 Redhat 官网注册与订阅 &#x1f30d;2.2 Redhat 登录已订阅账号 &#x1f30d;2.3 更换 Redhat 为阿里yum源 &#x1f30d;2.4 验证 &#x1f30a;1. 问题说明 使用 yum makecache 或 yum up…

内网渗透:端口转发(SSH隧道)

SSH&#xff1a;两台设备之间进行远程登录的协议&#xff08;SSH本身就是一个隧道协议&#xff09; 远程文件传输scp命令&#xff08;scp是基于SSH的&#xff09; 拓扑&#xff1a; SSH隧道搭建的条件 1.获取到跳板机权限 2.跳板机中SSH服务启动 SSH端口转发分类&#xff1…

【工具分享】SQLmap

文章目录 工具介绍安装方式环境准备安装 sqlmap 工具介绍 sqlmap 是一个非常强大的自动化 SQL 注入工具&#xff0c;主要用于渗透测试和安全审计。它能够检测和利用 SQL 注入漏洞&#xff0c;进而访问数据库服务器。 GitHub&#xff1a;https://github.com/sqlmapproject/sql…

为什么IP地址会被列入黑名单?

您是否曾经历过网站访客数量骤减或电子邮件投递失败的困扰&#xff1f;这背后或许隐藏着一个常被忽略的原因&#xff1a;您的IP地址可能已经被列入了黑名单内。尽管您并没有进行任何违法的网络操作&#xff0c;但这个问题依然可能出现。那么&#xff0c;究竟黑名单是什么&#…

通过Python脚本实现字符画

效果 讲解&#xff1a; 用于将3D视图的帧缓冲区转换为字符画&#xff0c;并将字符画输出到文本编辑器中。 首先&#xff0c;获取当前绑定的帧缓冲区、视口信息和视图像素。 然后&#xff0c;将像素矩阵转化为字符串&#xff0c;并将字符串写入到文本编辑器中。 设置文本编辑…

【Lua】第三篇:基本变量类型介绍

文章目录 一. 变量类型介绍二. 基本知识三. 基本类型介绍1. 空类型&#xff08;nil&#xff09;2. 数值类型&#xff08;number&#xff09;3. 字符串类型&#xff08;string&#xff09;4. 布尔类型&#xff08;boolean&#xff09; 一. 变量类型介绍 Lua中一共有如下8中变量…

Nosql期末复习

mongodb基本常用命令&#xff08;只要掌握所有实验内容就没问题&#xff09; 上机必考&#xff0c;笔试试卷可能考&#xff1a; 1.1 数据库的操作 1.1.1 选择和创建数据库 &#xff08;1&#xff09;use dbname 如果数据库不存在则自动创建&#xff0c;例如&#xff0c;以下…

设计模式 - 原型模式,就该这样学!

目录 开始 为什么要引入原型模式 原型模式概述 原型模式代码实现&#xff08;浅拷贝&#xff09; 浅拷贝和深拷贝的区别 原型模式代码实现&#xff08;深拷贝&#xff09; 方式一&#xff1a;直接 copy 方式二&#xff1a;序列化和反序列化&#xff08;推荐&#xff09…

ApolloClient GraphQL 与 ReactNative

要在 React Native 应用程序中设置使用 GraphQL 的简单示例&#xff0c;您需要遵循以下步骤&#xff1a; 设置一个 React Native 项目。安装 GraphQL 必要的依赖项。创建一个基本的 GraphQL 服务器&#xff08;或使用公共 GraphQL 端点&#xff09;。从 React Native 应用中的…

window下git bash设置启动后默认路径进入自己的工程

方法一&#xff1a;更改快捷方式 方法二&#xff1a;修改~/.bashrc