【问题标题】:Is there any way to save registers before jumping into function?有没有办法在进入功能之前保存寄存器?
【发布时间】:2019-03-16 11:05:46
【问题描述】:

这是我的第一个问题,因为我找不到与此主题相关的任何内容。

最近,在为我的 C 游戏引擎项目制作课程时,我发现了一些有趣的事情:

struct Stack *S1 = new(Stack);
struct Stack *S2 = new(Stack);

S1->bPush(S1, 1, 2);               //at this point

bPush是结构体中的一个函数指针。

所以我想知道,在这种情况下,运算符-> 是什么,我发现:

 mov         r8b,2                 ; a char, written to a low point of register r8
 mov         dl,1                  ; also a char, but to d this time
 mov         rcx,qword ptr [S1]    ; this is the 1st parameter of function
 mov         rax,qword ptr [S1]    ; !Why cannot I use this one?
 call        qword ptr [rax+1A0h]  ; pointer call

所以我假设 -> 写了一个指向 rcx 的对象指针,我想在函数中使用它(它们应该是方法)。所以问题是,我怎样才能做类似的事情

 push        rcx
 // do other call vars
 pop         rcx
 mov         qword ptr [this], rcx

在它开始写入函数的其他变量之前。有预处理器的东西?

【问题讨论】:

  • 这是一个很大的话题,但基本的答案是您确实在寄存器中传递(一些)参数。你需要了解的概念是calling conventions
  • @IlyaPakhmutov C 没有“这个”。您是否标记了错误的语言?
  • 那么,你为什么要关心编译器使用了哪些寄存器呢?
  • @interjay:我认为 Ilya 的意思是 MSVC 在针对 x86-64 时不允许/支持内联 asm,只有 32 位 x86。当然,阻止您使用内联 asm 的不是 x86-64,而是 Microsoft 的编译器。其他编译器(gcc、clang、ICC)在面向 x86-64 Windows 时都允许 GNU C 内联 asm 语法。但是你必须自己编写约束和破坏。
  • 如果有的话,我会试图说服 MSVC 使用它已经在 RCX 中拥有的S1 的副本作为call 寻址模式的基础,而不是浪费一条指令来加载再次 S1。我怀疑您正在查看未优化的代码,尽管 MSVC 可能在那里错过了优化。

标签: c assembly visual-c++ x86-64 calling-convention


【解决方案1】:

如果您使用 C++ 编写代码,看起来您会更轻松(并获得相同或更高效的 asm),这样您就可以使用语言对虚函数的内置支持,以及在初始化时运行构造函数。更不用说不必手动运行析构函数了。你不需要你的struct Class hack。

我想隐式传递*this 指针,因为如第二个 asm 部分所示,它两次做同样的事情,是的,这就是我要找的,bPush 是结构的一部分,它不能从外部调用,但我必须传递它已经拥有的指针 S1。

由于禁用了优化,您的 asm 效率低下。

MSVC -O2-Ox 不会重新加载静态指针两次。它确实浪费了在寄存器之间复制 mov 指令,但如果你想要更好的 asm,请使用更好的编译器(如 gcc 或 clang)。

Godbolt 编译器资源管理器中最古老的 MSVC 是来自 MSVC 2015 的 CL19.0,它编译此源代码

struct Stack {
    int stuff[4];
    void (*bPush)(struct Stack*, unsigned char value, unsigned char length);
};


struct Stack *const S1 = new(Stack);

int foo(){
    S1->bPush(S1, 1, 2);

    //S1->bPush(S1, 1, 2);
    return 0;  // prevent tailcall optimization
}

into this asm (Godbolt)

# MSVC 2015  -O2
int foo(void) PROC                                        ; foo, COMDAT
$LN4:
        sub     rsp, 40                             ; 00000028H
        mov     rax, QWORD PTR Stack * __ptr64 __ptr64 S1
        mov     r8b, 2
        mov     dl, 1
        mov     rcx, rax                   ;; copy RAX to the arg-passing register
        call    QWORD PTR [rax+16]
        xor     eax, eax
        add     rsp, 40                             ; 00000028H
        ret     0
int foo(void) ENDP                                        ; foo

(我在 C++ 模式下编译,因此我可以编写 S1 = new(Stack) 而无需复制您的 github 代码,并使用非常量初始化程序在全局范围内编写它。)

Clang7.0 -O3 立即加载到 RCX

# clang -O3
foo():
        sub     rsp, 40
        mov     rcx, qword ptr [rip + S1]
        mov     dl, 1
        mov     r8b, 2
        call    qword ptr [rcx + 16]          # uses the arg-passing register
        xor     eax, eax
        add     rsp, 40
        ret

奇怪的是,clang 仅在使用__attribute__((ms_abi)) 定位 Windows ABI 时才决定使用低字节寄存器。它使用mov esi, 1 来避免在针对其默认 Linux 调用约定而不是mov sil, 1 时出现错误的依赖关系。


或者,如果您正在使用优化,那是因为更旧的 MSVC 更糟糕。在这种情况下,您可能无法在 C 源代码中执行任何操作来修复它,尽管您可以尝试使用 struct Stack *p = S1 局部变量来手动将编译器加载到寄存器中并从那里重用它。)

【讨论】:

  • 这段代码是一个印象,我想找工作,但是一切都已经写好了,而且大多数情况下写出独特的东西几乎是不可能的。这就是为什么我更喜欢 C 来编写我的游戏引擎。我没有时间研究 10000 个框架和编写我很难理解的东西,我能给出的最好的东西是我的迷宫生成算法和带有 asm 的 C。我只是觉得这不会给我在 Gamedev 工作的机会。谢谢你的回答:)
  • @IlyaPakhmutov:我并不是说你应该在 C++ 中使用一些现有的库,语言本身确实具有核心语言中内置的这些特性(虚拟函数和构造函数),没有标准库。此外,您已经在使用一个 C++ 特性:非常量静态初始化器(用于全局 S1)。如果您使用 C 编译器而不是 C++ 编译错误消息,请参阅 godbolt.org/z/M_dCTH。 (我用一个函数原型模拟了你的 new 宏,该函数接受 void* 并返回 void*,将结果转换为有效的 C++。)
  • S1 不是全局的,它在 main() 中,我没有在 git 上列出它,我已经在 2 个编译器上启动了它——MSVC 和 GCC,都在 -C 中; new 不是宏,它是常规函数,是的,它需要 void* 以及构造函数的参数。
  • @IlyaPakhmutov:如果它在main里面,那么它的asm寻址模式怎么可能是qword ptr [S1]?是static吗?如果没有,它将存储在堆栈中(或优化掉),并且在 asm.xml 中不会有具有该名称的符号。您是否手动编辑了 asm 以将 [rsp + 40] 替换为 [S1] 或其他内容?
  • 不,只是 Visual Studio 给了我漂亮的反汇编代码清单。 Idk,也许这是它的特性,但 x86 给了我这个:S1->bPush(S1, 1, 2); mov esi,esp push 2 push 1 mov eax,dword ptr [S1] push eax mov ecx,dword ptr [S1] mov edx,dword ptr [ecx+170h] call edx
猜你喜欢
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-27
  • 1970-01-01
相关资源
最近更新 更多