Assembly 矩阵向量乘法
我当时正在用C编写一个AVX矩阵向量乘法函数,但意识到我需要的一条指令没有在GCC中实现,所以我认为这是学习一些x86汇编的黄金机会。我首先在MIPS汇编中编写了一个例程,然后尝试翻译它。我的代码不起作用,我犯了错误,我不知道为什么。如果我删除代码中的两个Assembly 矩阵向量乘法,assembly,nasm,x86-64,Assembly,Nasm,X86 64,我当时正在用C编写一个AVX矩阵向量乘法函数,但意识到我需要的一条指令没有在GCC中实现,所以我认为这是学习一些x86汇编的黄金机会。我首先在MIPS汇编中编写了一个例程,然后尝试翻译它。我的代码不起作用,我犯了错误,我不知道为什么。如果我删除代码中的两个jnz,它会工作,但我不明白为什么它们会有任何影响。这种类型的跳转会破坏我正在使用的寄存器吗 编辑:看来main中的前两条指令没有将rdi设置为2,而是设置为0x100000002,这会导致以后出现问题。为什么2没有加载 EDIT2:明白了。正
jnz
,它会工作,但我不明白为什么它们会有任何影响。这种类型的跳转会破坏我正在使用的寄存器吗
编辑:看来main
中的前两条指令没有将rdi
设置为2,而是设置为0x100000002
,这会导致以后出现问题。为什么2没有加载
EDIT2:明白了。正如@rkhb指出的,使用rXX寄存器加载的数据比我预期的要多。我将寄存器更改为32位寄存器(如果适用),这解决了SEGFULT问题。但是,现在程序打印了0,0。这是因为循环将eax提前了8(在下面的示例中),但在返回之前不会减去该值。因此,值位于addr
和addr+4
中,但返回的指针是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
数据声明为4字节,但8字节寄存器(
rax
etc)用于保存数据。当将数据加载到这些寄存器中时,您将在低4字节中加载数据,在高4字节中加载垃圾。为了避免这种情况,可以更改声明,以便声明8字节数据(使用dq
),或者使用4字节寄存器(eax
等)
修复该问题将解决分段错误的问题,但程序将返回错误的结果。结果将存储在内存地址c
,其值保存在rax
中。在上述代码中,该地址增加4字节两次;当计算值存储在c[0]
和c[1]
中时,rax
中返回的地址实际上是&c[2]
。因此,在返回到main
之前,通常需要将rax
减小8,或减小4*N
编辑:您可以使用GNU调试器检查寄存器,阅读指南。
mov rdi,[rdi]
加载8个字节。但是N:dd2
只有4个字节大。将dd
更改为dq
。谢谢。我想这可能与此有关,因此我做了更麻烦的更改,即将所有regs从rax更改为eax等。现在它可以工作,但在返回main后,rax中的值被归零,或者说,rax指向的值被归零。如果你已经解决了问题,你应该将其作为答案发布,而不是编辑你的问题。好的。我不认为这是必要的,但好的,我会发布一个答案。