这是我想要实现的目标:
a_x*b_x + a_y*b_y + a_z*b_z
我正在尝试在汇编中创建一个执行上述计算的MACRO。
我正在使用WORD
来获取我的所有号码。这是我的代码:
dotProduct MACRO A_X,A_Y,A_Z,B_X,B_Y,B_Z ;a.b (a dot b) = a_x*b_x + a_y*b_y + a_z*b_z
mov ah, A_X
mov al, B_X
imul ax
mov answer, ax
mov ah, A_Y
mov al, B_Y
imul ax
add answer, ax
mov ah, A_Z
mov al, B_Z
imul ax
mov answer, ax
output answer
ENDM
answer BYTE 40 DUP (0)
但我收到以下错误:
Assembling: plane_line.asm
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(1): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(2): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(4): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(5): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(6): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(8): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(9): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(10): Macro Called From
plane_line.asm(101): Main Line Code
plane_line.asm(101) : error A2070: invalid instruction operands
crossProduct(12): Macro Called From
plane_line.asm(101): Main Line Code
我认为这与我处理寄存器的方式有关。
我应该怎么做呢?
答案 0 :(得分:2)
MOV的两个操作数必须大小相同。 AL和AH是字节寄存器。
MASM样式的汇编程序根据您在符号名称后使用的DW
来推断内存位置的大小。这就是为什么它抱怨操作数大小不匹配(一般无用的错误消息也适用于许多其他问题)。
如果你真的想将A_X的第一个字节加载到AL,你可以使用覆盖:mov al, BTYE PTR A_X
。
但那不是你想要的,因为你确实想要加载16位数字。两个16位数的乘积可以高达32位(例如0xffff ^ 2是0xfffe0001)。所以只做32位数学可能是一个好主意。
您还错误地使用imul
:imul ax
设置DX:AX = AX * AX
(在一对寄存器中产生32位结果)。要将AH * AL相乘并在AX中得到结果,您应该使用imul ah
。请参阅insn ref manual entry for IMUL。另请参阅x86代码wiki中的文档和指南的其他链接。
IMUL的双操作数形式更易于使用。它与ADD完全相同,具有目标和源,产生一个结果。 (它不会将全部乘法结果的高半部分存储在任何地方,但这个用例很好。)
要设置32位IMUL,use MOVSX to sign-extend从DW 16位存储器位置到32位寄存器。
无论如何,这里有你应该做的事:
movsx eax, A_X ; sign-extend A_X into a 32-bit register
movsx ecx, B_X ; Use a different register that's
imul eax, ecx ; eax = A_X * B_X (as a 32-bit signed integer)
movsx edx, A_Y
movsx ecx, B_Y
imul edx, ecx ; edx = A_Y * B_Y (signed int)
add eax, edx ; add to the previous result in eax.
movsx edx, A_Z
movsx ecx, B_Z
imul edx, ecx ; edx = A_Z * B_Z (signed int)
add eax, edx ; add to the previous result in eax
我不确定你的"输出"函数/宏应该可以工作,但是将整数存储到字节数组BYTE 40 DUP (0)
中似乎不太可能。你可以用mov dword ptr [answer], eax
来做,但也许你应该output eax
。或者,如果output answer
将eax转换为answer
中存储的字符串,那么您首先不需要mov
。
我假设您的号码已签名 16位开头。这意味着如果所有输入均为INT16_MIN(即-32768 = 0x8000),则您的点积可能会溢出。 0x8000 ^ 2 = 0x40000000,超过INT32_MAX的一半。所以32位ADD并不是很安全,但我认为你可以用它并且不想添加携带。
另一种方式:我们可以使用16位IMUL指令,因此我们可以将其与内存操作数一起使用,而不必使用符号扩展单独加载。如果您确实需要完整的32位结果,那么这样就不那么方便了,所以我只是说明只使用低半部分。
mov ax, A_X
imul B_X ; DX:AX = ax * B_X
mov cx, ax ; save the low half of the result somewhere else so we can do another imul B_Y and add cx, ax
;or
mov cx, A_X
imul cx, B_X ; result in cx
有趣的方式:SSE4.1有一个SIMD水平点积指令。
; Assuming A_X, A_Y, and A_Z are stored contiguously, and same for B_XYZ
pmovsxwd xmm0, qword ptr [A_X] ; also gets Y and Z, and a high element of garbage
pmovsxwd xmm1, qword ptr [B_X] ; sign-extend from 16-bit elements to 32
cvtdq2ps xmm0, xmm0 ; convert in-place from signed int32 to float
cvtdq2ps xmm1, xmm1
dpps xmm0, xmm1, 0b01110001 ; top 4 bits: sum the first 3 elements, ignore the top one. Low 4 bits: put the result only in the low element
cvtss2si eax, xmm0 ; convert back to signed 32-bit integer
; eax = dot product = a_x*b_x + a_y*b_y + a_z*b_z.
这实际上可能比标量imul代码慢,特别是在每个时钟可以执行两次加载且具有快速整数乘法的CPU上(例如Intel SnB系列具有3个周期的imul r32, r32
延迟,每个周期1个吞吐量)。标量版本具有许多指令级并行性:加载和乘法是独立的,只有组合结果的加法相互依赖。
DPPS很慢(Skylake上4个uop和13c延迟,但每1.5c吞吐量仍然有一个)。
整数SIMD点积(仅需要SSE2):
;; SSE2
movq xmm0, qword ptr [A_X] ; also gets Y and Z, and a high element of garbage
pslldq xmm0, 2 ; shift the unwanted garbage out into the next element. [ 0 x y z garbage 0 0 0 ]
movq xmm1, qword ptr [B_X] ; [ x y z garbage 0 0 0 0 ]
pslldq xmm1, 2
;; The low 64 bits of xmm0 and xmm1 hold the xyz vectors, with a zero element
pmaddwd xmm0, xmm1 ; vertical 16b*16b => 32b multiply, and horizontal add of pairs. [ 0*0+ax*bx ay*by+az*bz garbage garbage ]
pshufd xmm1, xmm0, 0b00010001 ; swap the low two 32-bit elements, so ay*by+az*bz is at the bottom of xmm1
paddd xmm0, xmm1
movd eax, xmm0
如果可以保证A_Z之后和B_Z之后的2个字节为零,则可以省略PSLLDQ byte-shift instructions。
如果您不必将垃圾字从低64位移出,您可以在MMX寄存器中执行此操作,而不需要MOVQ负载将64位零扩展为128位寄存器。然后你可以用内存操作数PMADDWD。但是你需要EMMS。此外,MMX已过时,pmaddwd mm, mm
Skylake has lower throughput比pmaddwd xmm,xmm
(或256b ymm)更早。
除了PMADDWD的5个周期外,这里的所有内容都是近期英特尔的单周期延迟。 (MOVD是2个周期,但你可以直接存储到内存中。负载显然也有延迟,但它们来自固定地址,所以没有输入依赖。)