2017-06-07 2 views
2

J'écrivais une fonction de multiplication matricielle-vectorielle AVX en C mais j'ai réalisé qu'une instruction dont j'avais besoin n'était pas implémentée dans GCC, donc j'ai vu cela comme une occasion en or d'apprendre l'assemblage x86. J'ai d'abord écrit une routine dans l'assemblage MIPS, puis j'ai essayé de le traduire. Mon code ne fonctionne pas, je me suis trompé et je ne sais pas pourquoi. Si je supprime les deux jnz dans le code, cela fonctionne, mais je ne comprends pas pourquoi ils auraient un impact. Ce type de saut détruit-il les registres que j'utilise?Multiplication matricielle-vectorielle NASM

EDIT: Il semble que les deux premières instructions dans main ne définissent pas rdi sur 2, mais plutôt sur 0x1000000002, ce qui provoque des problèmes plus tard. Pourquoi est-ce que 2 n'est pas chargé?

EDIT2: J'ai compris. Comme l'a souligné @rkhb, l'utilisation de registres rXX charge plus de données que ce que j'avais prévu. J'ai changé les registres à ceux de 32 bits (le cas échéant), ce qui a résolu le problème segfault. Cependant, maintenant le programme imprimé 0,0. C'est parce que la boucle avance eax de 8 (dans l'exemple ci-dessous), mais ne soustrait pas ce montant avant de revenir. Ainsi, les valeurs sont dans et addr+4, mais le pointeur renvoyé est addr+8.

; nasm -felf64 filename.asm 
; gcc filename.o 

    global main 
    extern printf 

    section .data 
N: dd 2   ; dimension 
a: dd 1, 2, 3, 4  ; matrix 
b: dd 1, 2   ; vector 
format: db "%d", 10, 0 

    section .bss 
c: resb 8   ; reserve 8B 

    section .text 
main: 
    ; set up arguments 
    lea rdi, [N] ; fix: change regs to edi, etc 
    mov rdi, [rdi] 
    lea rsi, [a] 
    lea rdx, [b] 
    lea rcx, [c] 

    call matvec  ; c = a*b 

    ; print results 
    mov rsi, [rax] 
    mov rdi, format 
    push rax 
    mov rax, 0 
    call printf  ; print c[0], should be 5 
    pop rax 
    add rax, 4 
    mov rsi, [rax] 
    mov rdi, format 
    mov rax, 0 
    call printf  ; print c[1], should be 11 

    ret 

; rdi = N, rsi = int*, rdx = int*, rcx = int* 
matvec: 
    mov rax, rcx ; rax = c 
    mov R14, rdi ; r14 = N 
    mov R15, R14 
    shl R15, 2  ; r15 = 4*N 
    xor R8, R8  ; i = 0 
    xor R9, R9  ; j = 0 
    xor R10, R10 ; sum = 0 

loop: 
    mov R11, [rsi] ; r11 = *a 
    mov R12, [rdx] ; r12 = *b 
    imul R11, R12 ; r11 *= r12 
    add R10, R11 ; r10 += r11 
    add rsi, 4  ; a++ 
    add rdx, 4  ; b++ 
    add R9, 1  ; j++ 
    cmp R14, R9 
    jnz loop  ; loop while r14-r9 = N-j != 0 

    mov [rax], R10 ; *c = sum 
    xor R10, R10 ; sum = 0 
    xor R9, R9  ; j = 0 on every i loop 
    sub rdx, R15 ; b -= 4*N 
    add rax, 4  ; c++ 
    add R8, 1  ; i++ 
    cmp R14, R8 
    jnz loop  ; loop while r14-r8 = N-i != 0 

    sub rax, R15 ; fix: subtract 4*N from return pointer 
    ret 
+1

'mov rdi, [rdi]' charge 8 octets. Mais 'N: dd 2' est juste 4 octets gros. change 'dd' en' dq'. – rkhb

+0

Merci. J'ai pensé que cela pourrait avoir quelque chose à voir avec ça et j'ai donc fait le changement le plus encombrant, à savoir changer tous les regs de rax à eax etc. Maintenant ça marche mais après retour à main, les valeurs dans rax sont mises à zéro, ou plutôt les points sont mis à zéro. –

+0

Si vous avez résolu le problème, vous devriez le poster comme une réponse plutôt que comme une modification à votre question. –

Répondre

0

Les données est déclaré comme 4 octets, mais on utilise des registres de 8 octets (rax etc) pour maintenir les données. Lors du chargement des données dans ces registres, vous aurez ensuite vos données dans les 4 octets bas et les ordures dans les 4 octets élevés. Pour éviter cela, modifiez les déclarations afin que les données de 8 octets soient déclarées (en utilisant dq) ou utilisez plutôt les registres de 4 octets (eax etc.).

Fixation qui s'occupera du problème de segmentation, mais le programme retournera le mauvais résultat. Le résultat est destiné à être stocké à l'adresse mémoire c, dont la valeur est détenue dans rax. Dans le code ci-dessus, cette adresse est incrémentée de 4 octets deux fois; tandis que les valeurs calculées sont stockées dans c[0] et c[1], l'adresse renvoyée dans rax est en réalité &c[2]. Ainsi, avant de retourner à main, vous devez décrémenter rax par 8, ou par 4*N en général.

EDIT: Vous pouvez inspecter les registres à l'aide du débogueur GNU, lire le guide this.