简体   繁体   English

通过遵循每个通用寄存器的目的对 x86 汇编进行编码是否有必要或更容易

[英]Is it necessary or easier to code x86 assembly by following the purpose of each General Purpose register

In general, Is it necessary or easier to code x86 assembly by following the purpose of each register?一般来说,按照每个寄存器的用途编写 x86 汇编代码是否必要或更容易?

The registers in x86 architecture were each first designed to have a special purpose, but compilers modernly doesn't seems to care their usage(unless under some special condition such as REP MOV or MUL). x86 架构中的寄存器最初被设计为具有特殊用途,但现代编译器似乎并不关心它们的使用(除非在某些特殊条件下,例如 REP MOV 或 MUL)。

so, will it be easier or more optimize to code depend on the purpose of each registers?(regardless of the special instructions(or encoding) that are identical to some register)那么,根据每个寄存器的用途,代码会更容易或更优化吗?(不管与某些寄存器相同的特殊指令(或编码)如何)

For instance(I could use REP MOVSB or LODSB STOSB instead, but just to demonstrate):例如(我可以使用 REP MOVSB 或 LODSB STOSB 代替,但只是为了演示):

1st Code:第一个代码:

LEA ESI,[AddressOfSomething]
LEA EDI,[AddressOfSomethingElse]
MOV ECX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ESI]
ADD AL,8
MOV [EDI],AL
ADD ESI,1
ADD EDI,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...

2nd Code:第二个代码:

LEA ECX,[AddressOfSomething]
LEA EDX,[AddressOfSomethingElse]
MOV EBX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ECX]
ADD AL,8
MOV [EDX],AL
ADD ECX,1
ADD EDX,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...

The Compiler I used--Visual Studio 2015 usually uses the 2nd method when doing tasks such as this, it doesn't use registers depend on its' purpose, instead, the compiler only choose what register to use based on its' "volatile" or "non-volatile" characteristic(after calling a function).我使用的编译器——Visual Studio 2015 在执行此类任务时通常使用第二种方法,它不使用寄存器取决于其目的,相反,编译器仅根据其“易失性”选择要使用的寄存器或“非易失性”特性(调用函数后)。 Because of this, all the high-level-programming-language programmed software disassembly use the 2nd method.正因为如此,所有的高级编程语言编程的软件反汇编都使用第二种方法。

Another interesting fact is that in ARM language, the GPRs all serves the same purpose, and are named R0-R7, which means that when code with it, the code will be more similar to 2nd code.另一个有趣的事实是,在 ARM 语言中,GPR 都具有相同的用途,并被命名为 R0-R7,这意味着使用它进行编码时,代码将更类似于第二个代码。

All in all, my opinion is that these two codes uses the same instructions, therefore it should have same speed regardless of what register I used.总而言之,我认为这两个代码使用相同的指令,因此无论我使用什么寄存器,它都应该具有相同的速度。 But am I correct?但我说得对吗? and which code is easier to code with?哪个代码更容易编码?

Following the purpose of each register primarily achieves:遵循各个寄存器的目的主要达到:

  • Code density代码密度

    For example using the A register 1 usually reduces the code size for common operations like moving, arithmetic, logic and IO 2 .例如,使用A寄存器1通常会减少移动、算术、逻辑和 IO 2等常见操作的代码大小。
    Using the C register for counting let you exploit the jcxz family of instructions, avoiding an explicit compare.使用C寄存器进行计数可让您利用jcxz系列指令,避免显式比较。
    movsd and similar are very "dense" instructions they perform complex operations that otherwise would require a lot of code. movsd和类似的指令是非常“密集”的指令,它们执行复杂的操作,否则将需要大量代码。

    However code density doesn't mean "faster" due to the fact that x86 is blatantly CISC, a complex instruction can take more time to execute than an equivalent series of simpler instructions 3 .然而,代码密度并不意味着“更快”,因为 x86 公然是 CISC,一个复杂的指令可能比等效的一系列更简单的指令3需要更多的时间来执行。

  • Readibility可读性

    An instruction like rep movsd effectively is an "high level" way of coding a cycle that moves data from a source to a destination.rep movsd这样的指令实际上是一种编码循环的“高级”方式,该循环将数据从源移动到目标。
    Parsing the cycle解析循环

    push eax pushf .loop: mov eax, DWORD [esi] mov DWORD [es:edi], eax add esi, 4*(1-D*2) add edi, 4*(1-D*2) dec ecx jnz .loop popf pop eax

    is a lot more difficult.要困难得多。

  • Idiomatic programming惯用编程

    The use of SP as a stack pointer is assumed by a lot of instructions ( call , ret , push , ...).许多指令( callretpush ……)都假定使用SP作为堆栈指针。
    It is possible to avoid using SP as a stack pointer, but it wouldn't be very idiomatic (nor efficient).可以避免使用SP作为堆栈指针,但它不会很惯用(也不高效)。

  • Less data moving更少的数据移动

    In real mode, only a few registers could be used as a base (one of them being the B register).在实模式下,只有少数寄存器可以用作基址(其中之一是B寄存器)。
    Keeping addresses in B from the beginning would avoid moving them into it later.从一开始就将地址保留在B可以避免以后将它们移到B Though register-register moves don't need an execution unit today, they make the source harder to read 4 .尽管现在寄存器-寄存器移动不需要执行单元,但它们使源代码更难阅读4

Most of the idiomatic register usages have been relaxed today 5 because too much specific purpose registers reduce the optimisations a compiler can do (and spilling onto the stack is expensive).大多数惯用的寄存器用法在今天已经放宽了5,因为太多的特定用途寄存器减少了编译器可以做的优化(并且溢出到堆栈上是昂贵的)。

CPUs are very complex, if you want to write code for speed then you should consider speed metrics only. CPU 非常复杂,如果您想为速度编写代码,那么您应该只考虑速度指标。 Idiomatic register usage is not one of them, for one thing that there is not a single A , B or C register at the micro-architecture level so "registers" as the programmer see them is only a human concept (well, and a front-end concept).惯用的寄存器用法不是其中之一,因为在微架构级别没有单个ABC寄存器,所以程序员看到的“寄存器”只是一个人类概念(好吧,还有前面-结束概念)。


1 In its forms AL , AX , EAX , RAX 1以其形式ALAXEAXRAX
2 mov A, [mem] uses the opcodes A0 or A1 , while mov B, [mem] uses 8A 1E or 8B 1E . 2 mov A, [mem]使用操作码A0A1 ,而mov B, [mem]使用8A 1E8B 1E The same is true for add and similar. add和 similar 也是如此。 in , out , div , mul enforce the use of A . in , out , div , mul强制使用A
3 But not to fetch and decode. 3但不是去取和解码。
4 Is there an equivalent of "Spaghetti code" for data moves into registers? 4是否有相当于“意大利面条代码”的数据移动到寄存器中?
5 Consider for example the various addressing mode or the imul instruction 5考虑例如各种寻址模式或imul指令

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM