在调用函数

如何解决在调用函数

TL;DR; 我正在寻找一种标准的方法来基本上告诉编译器将给定寄存器中发生的任何事情传递给下一个函数。 >

基本上我有一个函数 int bar(int a,int b,int c)。在某些情况下,c 未使用,我希望能够在 bar 未使用的情况下调用 c,而无需以任何方式修改 rdx

例如如果我有

int foo(int a,int b) { 
    int no_init; 
    return bar(a,b,no_init); 
}

我希望程序集如下:

对于尾声

    jmp bar

或正常通话

   call bar

注意:clang 通常会产生我正在寻找的东西。但我不确定在更复杂的函数中是否总是如此,我希望每次构建时都不必检查程序集。

GCC 产生:

对于尾声

    xorl %edx,%edx
    jmp bar

或正常通话

   xorl %edx,%edx
   call bar

我可以使用内联汇编获得我想要的结果,即将 foo(用于尾调用)更改为

int foo(int a,int b) {
    asm volatile("jmp bar" : : :);
    __builtin_unreachable();
}

编译为只是

   jmp bar

我了解 xorl %edx,%edx 对性能的影响尽可能接近 0,但是

我想知道是否有标准方法可以实现这一点。

也就是说,对于任何给定的情况,我都可以找到适合它的破解方法。但这将需要我每次验证程序集。我正在寻找一种方法,您可以基本上告诉编译器“传递寄存器中发生的任何事情”。

查看示例:https://godbolt.org/z/eh1vK8

编辑:这发生在 -O3 集上。

解决方法

我想知道是否有标准方法可以实现这一点。

也就是说,对于任何给定的情况,我都可以找到适合它的破解方法。但那 每次都需要我验证程序集。我正在寻找一个 基本上可以告诉编译器“通过任何 碰巧在注册”。

不,没有在 C 或 C++ 中实现它的标准方法。这两种语言都不涉及任何低级函数调用语义,甚至都不承认 CPU 寄存器的存在,* 并且这两种语言都要求每次函数调用都提供对应于所有非可选参数的参数(其中在 C 中只是“所有声明的参数”)。

例如如果我有

int foo(int a,int b) { 
    int no_init; 
    return bar(a,b,no_init); 
}

...然后您会获得未定义行为,因为在不确定的情况下使用 no_init 的值。无论任何特定的 C 或 C++ 实现都接受它,根据定义,它是非标准的。

如果你想调用bar(),但你并不关心传递什么值作为第三个参数,那么为什么不选择一个方便的值来传递呢?零,例如:

    return bar(a,0); 

*就任一语言标准而言,即使是 register 关键字也无法做到这一点。

,

请注意,如果被调用的函数确实读取了它的第 3 个参数,那么将其保留为未写入的风险会导致对上次使用的任何 EDX 产生错误依赖。例如,它可能是缓存未命中加载或长链计算的结果。

GCC 小心地异或零以在很多情况下打破错误的依赖关系,例如在 cvtsi2ss(糟糕的 ISA 设计)或 popcnt(Sandybridge 系列怪癖)之前。

通常 xor edx,edx 基本上是浪费的 2 字节 NOP,但它确实防止了其他独立依赖链(关键路径)的可能耦合。

如果您确定要挫败编译器保护您免受这种情况的尝试,那么 Nate 的 asm("" :"=r"(var)); 是一个很好的方法来实现 _mm_undefined_ps() 的整数版本,它实际上会留下未初始化的寄存器。 (请注意,_mm_undefined_ps 并不能保证不写入 XMM reg;一些编译器会为您进行异或零,而不是完全实现内在旨在允许英特尔编译器的虚假依赖鲁莽。)

,

将函数转换为具有更小的签名(即更少的参数):

extern int bar(int,int,int);

int foo(int a,int int b) {
    return ((int (*)(int,int))bar)(a,b); 
}

也许可以为2个参数栏做一个宏,甚至去掉foo

extern int bar3(int,int);

#define bar2(a,b) ((int (*)(int,int))bar3)(a,b)

int userOfBar(int a,int b) { return bar2 (a,b); }

https://godbolt.org/z/Gn4a69

奇怪的是,鉴于上面的 gcc 没有触及 %edx,但 clang 确实......哦,好吧。

(仍然不能保证编译器不会触及某些寄存器,不过,这是它的领域。否则,您可以直接在汇编中编写这些函数并避免中间人。)

,

在大多数平台上适用于 gcc/clang 的一种方法是这样做

    int no_init; 
    asm("" : "=r" (no_init));
    return bar(a,no_init); 

这样您就不必就 bar 的原型向编译器撒谎(这可能会破坏一些调用约定),并且您可以欺骗编译器认为 no_init 确实已初始化。

我想知道像安腾这样的体系结构具有“陷阱位”,当访问未初始化的寄存器时会导致错误。此代码在那里可能不安全。

据我所知,没有可移植的方法来获得这种行为,但是您可以 ifdef:

#ifdef __GNUC__
#define UNUSED_INT ({ int x; asm("" : "=r" (x)); x; })
#else
#define UNUSED_INT 0
#endif
// ...
   bar(a,UNUSED_INT);

然后,您可以在必要时退回到(无限)效率较低但正确的代码。

它会在 gcc/x86-64 上生成一个裸 jmp,请参阅 https://godbolt.org/z/d3ordK。在 x86-32 上,它不是最佳选择,因为它推送未初始化的寄存器,而不仅仅是调整 esp 的现有减法。请注意,裸 jmp/call 在 x86-32 上是不安全的,因为第三个堆栈槽可能包含一些重要的东西,并且允许被调用者覆盖它(即使该变量在您想到的路径上未使用,编译器可能会将其用作暂存空间)。

一种可移植的替代方法是将 bar 重写为可变参数。但是,当第三个参数存在时,它需要使用 va_arg 来检索第三个参数,这往往效率较低。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)> insert overwrite table dwd_trade_cart_add_inc > select data.id, > data.user_id, > data.course_id, > date_format(
错误1 hive (edu)> insert into huanhuan values(1,'haoge'); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive> show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 <configuration> <property> <name>yarn.nodemanager.res