Попытка сделать два счетчика с одним регистром - PullRequest
2 голосов
/ 17 апреля 2019

Я хочу использовать 64-битный регистр для управления двумя 32-битными счетчиками во вложенном цикле

Я пытаюсь контролировать счетчики с помощью команды поворота в сборке плюс некоторые значения xor, но моя проблема в том, что когда я перевожу ECX, часть HIGHER поворачивается в 0, а мой EXTERNAL счетчик находится в части HIGHER. Я тоже пытался DEC от CL, но когда последний БАЙТ повернул 0, DEC превратил его в 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

Может быть, есть какой-нибудь способ сделать DEC в ECX, который не фитиль в верхней части

Ответы [ 3 ]

1 голос
/ 17 апреля 2019

Это работает:

                                        ;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
0 голосов
/ 18 апреля 2019

Запись 32-битного регистра всегда обнуляет верхние 32 полного 64-битного регистра. Вы могли бы сделать этот трюк легче с 16-битными половинами 32-битного регистра или особенно с младшими 8 битами.

( В задаче с кодом-гольфом , у меня когда-то была константа, в которой я нуждался только вне циклов, а ее младшие 8 бит были равны нулю. Я использовал ebx=-1024 вне внутренних циклов и bl как мой счетчик циклов внутри циклов, оканчивающийся на bl = 0.)

Но обычно лучше просто использовать другой регистр или оставить счетчик внешнего цикла в памяти стека. (Или вывести другое редко используемое значение, особенно если оно в основном для чтения, так что вы можете просто использовать это как операнд источника памяти.)

Как предлагает Шут, тестируйте младшие 32 бита отдельно для состояния внутреннего цикла. (Это стоит 1 дополнительный моп в семействе Intel Sandybridge, где dec/jnz может макро-слиться. Но 0 дополнительных моп в AMD или другом Intel, где dec/jnz не может слиться, но test/jnz может.)

Для внешнего цикла rcgldr уже предложил вращать до / после для замены 32-битных половин. (При неудачном выборе медленная loop инструкция без веской причины.)

Но мы можем уменьшить это значение до 1 инструкции overhead помимо sub / jcc, который у вас обычно был бы . Если мы рассматриваем внешний счетчик как со знаком 32-бит и проверяем, чтобы он стал отрицательным, мы можем сделать эту проверку одновременно с воссозданием счетчика внутреннего цикла в ECX с тем же sub rcx , (Это означает, что начальное значение счетчика должно быть на 1 ниже, потому что мы фактически останавливаемся на -1 вместо 0.)

32-битный немедленный с расширенным знаком недостаточно велик, чтобы sub rcx, 1<<32, и (если вам не нужна эта константа для чего-то другого), если вы используете 2 регистра, вам гораздо лучше использовать отдельные регистры для отдельные счетчики. Но с 2 вычитаниями, или на самом деле add из -(2^31), мы можем обернуть низкие 32 почти полностью, вычитая 1 из верхней половины и оставляя счет для следующего внутреннего цикла в ECX.

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)

Конечное состояние: rdx = 0x1234, rax = 0x6260060 = 0x1234 * 0x5678, поэтому эти циклы выполнялись правильное число раз.

В семействе Sandybridge sub / jge может слиться в одну инструкцию. Несмотря на это, я думаю, что у него худший размер кода (2x sub r64, imm32), а ror rcx,32 - инструкция с одним битом для семейства Sandybridge и AMD. (https://agner.org/optimize/). Если ваш внешний счетчик был в формате RAX, краткая форма кодирования без байта ModRM могла бы помочь.

Это работает для любого внутреннего счетчика без знака, от 1 до 2^32 - 1, и для любого внешнего счетчика со знаком, от 1 до 2^31 - 1.

Внутренний счет не может быть 0 = 2 ^ 32, потому что это потребует 2x add rcx, 0xFFFFFFFF80000000, чтобы обернуть все вокруг. С одной из инструкций, равной sub rcx,imm32, наибольшее положительное число (без установки старших битов), которое мы можем вычесть, составляет 0x7fffffff.

Это также может работать с jnc, если мы используем обертывание верхней половины в качестве условия выхода из цикла, позволяя полный диапазон 2 ^ 32-1 для верхнего счетчика.


30-битный счетчик внизу RCX, 34-битный счетчик вверху

Тест внутреннего цикла становится

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

Преимущество здесь в том, что один sub imm32 может обернуть внутренний счетчик туда, где он нам нужен:

sub   rcx,  (1<<31) - inner_count    ; outer-- and re-create the inner loop counter
jnc   .outer

Мы все еще не можем использовать jnz, потому что повторное создание внутреннего счетчика одновременно означает, что весь регистр не будет равен нулю. Таким образом, мы должны разветвляться на это, становясь отрицательным или имея неподписанный перенос.

0 голосов
/ 18 апреля 2019

К счастью @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
...