Есть ли способ сохранить регистры, прежде чем перейти в функцию? - PullRequest
3 голосов
/ 16 марта 2019

это мой первый вопрос, потому что я не смог найти ничего, связанного с этой темой.

Недавно, когда я делал урок для своего проекта игрового движка 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

до того, как оно начнет писать другие переменные функции.Что-то с препроцессором?

1 Ответ

2 голосов
/ 16 марта 2019

Похоже, вам было бы легче (и вы получите asm, который такой же или более эффективный), если бы вы писали на C ++, чтобы вы могли использовать встроенную поддержку языка для виртуальных функций и для запуска конструкторов при инициализации. Не говоря уже о том, чтобы не запускать деструкторы вручную. Вам не понадобится ваш struct Class хак.

Я бы хотел неявно передать указатель *this, потому что, как показано во второй части asm, он делает то же самое дважды, да, это то, что я ищу, bPush является частью структуры и не может вызываться извне, но мне нужно передать указатель S1, который у него уже есть.

Вы получаете неэффективный Asm, потому что вы отключили оптимизацию.

MSVC -O2 или -Ox не перезагружает статический указатель дважды. Он тратит впустую mov инструкцию копирования между регистрами, но если вы хотите лучше asm, используйте лучший компилятор (например, gcc или clang).

Самым старым MSVC в проводнике компилятора Godbolt является CL19.0 из MSVC 2015, который компилирует этот источник

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
}

в этот асм (Годболт)

# 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 решает использовать только младшие байты регистров при назначении Windows ABI с __attribute__((ms_abi)). Он использует mov esi, 1, чтобы избежать ложных зависимостей при настройке соглашения о вызовах в Linux по умолчанию, а не mov sil, 1.


Или, если вы используете оптимизацию, то это потому, что даже более старый MSVC еще хуже. В этом случае вы, вероятно, не сможете ничего сделать в исходном коде C, чтобы исправить это, хотя вы можете попробовать использовать локальную переменную struct Stack *p = S1, чтобы вручную удерживать компилятор в загрузке его в регистр и повторном его использовании оттуда.)

...