Trying to make two counters with one register

时间:2019-04-17 00:23:12

标签: assembly x86-64

I wanna to use an 64 bit reg to control two 32 bits counter in an nested loop

I'm trying to control counters with rotate command in assembly plus some xor's, but my problem is that when i sub an ECX the HIGHER part turn 0, and my EXTERNAL counter is in HIGHER part. I've tried to DEC from CL too, but when last BYTE turn 0, the DEC turn it to 0xFF

xor rcx, rcx ; i e j
mov ecx, 1000 ; i

for_ext:
    rol rcx, 32 ; j
    or rcx, 1000
    for_int:

        <some code>

    ; dec ecx ; <- this puts ZERO in HIGHER
    ; sub cl, 1 ; <- this works partially
    ; jnz for_int 
    ; loop for_int ; <- this test RCX, so don't work 

    rol rcx, 32
loop for_ext

Maybe have some way to made an DEC in ECX that don't wick in higher part

3 个答案:

答案 0 :(得分:1)

这有效:

                                        ;mov ecx,... clears upper bits of rcx
        mov     ecx,000000200h          ;run outer loop 200h times
main0:  rol     rcx,32
        or      rcx,000001000h          ;run inner loop 1000h times
main1:  nop
        dec     rcx
        test    ecx,ecx
        jnz     main1
        rol     rcx,32
        dec     rcx                     ;faster than loop
        jnz     main0

答案 1 :(得分:0)

非常感谢@Jester和其他人,我已经看过这段代码

segment .data
z dq 0

segment .text
global main:

main:

xor rax, rax ; res
xor rcx, rcx ; i e y
mov ecx, 1000 ; i

for_ext:
    rol rcx, 32 ; y
    or rcx, 1000 ; cl para nao zerar a parte alta
    for_int:

        <some code>

    dec rcx
    cmp ecx, 0
    jnz for_int

    rol rcx, 32
loop for_ext

ret

答案 2 :(得分:0)

始终写入32位寄存器 将整个64位寄存器的高32位置零。使用32位寄存器的16位一半,尤其是低8位,您可以更轻松地完成此操作。

In a code-golf problem,我曾经有一个常数,我只需要在循环外使用,它的低8位全为零。我在内部循环外使用ebx=-1024,将bl用作我的循环计数器在循环内,以bl = 0结尾。)

但是通常情况下,最好只使用另一个寄存器,或者将外部循环计数器保留在堆栈内存中。(或者溢出其他一些很少使用的值,尤其是在大多数情况下,这样您就可以用作存储源操作数。)

正如杰斯特(Jester)所建议的,请针对内部循环条件分别测试低32位。 (这在Intel Sandybridge系列上花费了1个额外的uop,其中dec/jnz可以进行宏融合。但是在AMD或其他Intel上,额外的0 uop则dec/jnz无法融合,但是test/jnz可以。)

对于外循环,rcgldr已经提议在旋转之前/之后进行旋转以交换32位的一半。 (不幸的是,the slow loop instruction的选择没有充分的理由。)

但是我们可以将其减少到通常只有sub / jcc之外的1条指令开销。如果我们将外部计数器视为32位 signed ,并检查其是否为负,则可以在执行该检查的同时,使用相同的{{ 1}}。 (这意味着初始计数器值需要低1,因为我们实际上是在sub rcx而不是-1处停止。)

32位符号扩展的立即数不足以0,并且(除非您需要该常数用于其他操作),如果使用2个寄存器,则最好使用单独的寄存器单独柜台的注册。但是用2减去,或者实际上是sub rcx, 1<<32的{​​{1}},我们可以将低位32几乎一路环绕,从高位一半减去1,然后将计数留给ECX中的下一个内部循环。

add

最终状态:-(2^31)inner_count equ 0x5678 outer_count equ 0x1234 global _start _start: xor eax, eax xor edx, edx ; test counters to prove this loops the right number of times mov rcx, ((outer_count-1)<<32) + inner_count .outer: .inner: ; do { ; ... inner loop body inc rax ; instrumentation: inner++ dec rcx ; rcx-- test ecx,ecx jnz .inner ; }while(ecx) ; ecx=0. rcx=outer count << 32 ;... outer loop body inc rdx ; instrumentation: outer++ add rcx, -1<<31 ; largest magnitude 32-bit immediate is INT_MIN, 0xFFFFFFFF8000000 sub rcx, (1<<31) - inner_count ; re-create the inner loop counter from 0 + INT_MIN jge .outer .end: ; set a breakpoint on _start.end and look at registers mov eax, 231 syscall ; Linux sys_exit_group(edi=0) ,因此这些循环运行了正确的次数。

在Sandybridge系列上,rdx = 0x1234 / rax = 0x6260060 = 0x1234 * 0x5678可以将宏融合为一条指令。即使这样,我认为它的代码大小也更糟(2x sub r64,imm32),而sub是Sandybridge系列和AMD的单指令。 (https://agner.org/optimize/)。如果您的外部计数器使用RAX,则无ModRM字节的简短格式编码会有所帮助。

这适用于任何从1到jge的无符号内部计数,以及任何从1到ror rcx,32的有符号正外部计数。

内部计数不能为0 = 2 ^ 32,因为这将需要2x 2^32 - 1来包装。在其中一条指令为2^31 - 1的情况下,我们可以减去的最大正数(未设置高位)为add rcx, 0xFFFFFFFF80000000

如果我们使用将上半部分作为循环退出条件,从而允许上计数器的完整范围为2 ^ 32-1,则这也可以与sub rcx,imm32一起使用。


RCX底部的30位计数器,顶部的34位计数器

内循环测试变为

0x7fffffff

这里的优点是单个jnc可以将内部计数器包装到我们需要的位置:

dec   rcx
test  ecx, (1<<31)-1      ; test the low 30 bits for non-zero
jnz  .inner

我们仍然不能使用sub imm32,因为同时重新创建内部计数意味着整个寄存器将不会为零。因此,我们必须将其变为负值或进行无符号环绕。