MOV 的两个操作数必须具有相同的大小。 AL 和 AH 是字节寄存器。
MASM 风格的汇编器从内存位置的大小推断DW
您在符号名称后使用。这就是为什么它抱怨操作数大小不匹配(带有通用的无用错误消息,该消息也适用于许多其他问题)。
如果您确实想将 A_X 的第一个字节加载到 AL 中,则可以使用覆盖:mov al, BTYE PTR A_X
.
但这不是您想要的,因为您实际上想要加载 16 位数字。两个 16 位数字的乘积最多可达 32 位(例如 0xffff^2 为 0xfffe0001)。因此,只进行 32 位数学计算可能是个好主意。
您也在使用imul
错误地:imul ax
sets DX:AX = AX * AX
(在一对寄存器中生成 32 位结果)。要将 AH * AL 相乘并得到 AX 中的结果,您应该使用imul ah
。请参阅IMUL 的 insn ref 手动输入 http://www.felixcloutier.com/x86/IMUL.html。另请参阅以下文档和指南的其他链接x86 /questions/tagged/x86标签维基。
IMUL 的双操作数形式更易于使用。它的工作原理与 ADD 完全相同,具有目的地和源,产生一个结果。 (它不会在任何地方存储全乘结果的高半部分,但这对于这个用例来说很好)。
要设置 32 位 IMUL,使用 MOVSX 进行符号扩展 http://www.felixcloutier.com/x86/MOVSX:MOVSXD.html从 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
first.
我假设你的号码是signed从 16 位开始。这意味着如果所有输入都是,您的点积可能会溢出INT16_MIN http://en.cppreference.com/w/cpp/types/integer(即-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 系列具有imul r32, r32
3 个周期的延迟,每个周期 1 个吞吐量)。标量版本具有大量指令级并行性:加载和乘法是独立的,只有组合结果的加法相互依赖。
DPPS 很慢(Skylake 上有 4 uops 和 13c 延迟,但每 1.5c 吞吐量仍然有 1 个)。
整数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 字节移位指令 http://www.felixcloutier.com/x86/PSLLDQ.html.
如果您不必将一个垃圾字移出低 64 位,则可以在 MMX 寄存器中有效地执行此操作,而不需要 MOVQ 加载来将 64 位零扩展为 128 位寄存器。然后你可以用内存操作数来PMADDWD。但接下来您需要 EMMS。另外,MMX 已经过时了,并且Skylake 吞吐量较低 http://agner.org/optimize/ for pmaddwd mm, mm
比pmaddwd xmm,xmm
(或 256b ymm)。
这里的所有内容都是最新 Intel 上的一个周期延迟,除了 PMADDWD 的 5 个周期。 (MOVD 是 2 个周期,但您可以直接存储到内存中。负载显然也有延迟,但它们来自固定地址,因此不存在输入依赖性。)