"invalid instruction operands" on mov ah, word_variable, 并在 16 位数字上使用 imul
"invalid instruction operands" on mov ah, word_variable, and using imul on 16-bit numbers
这是我想要实现的目标:
a_x*b_x + a_y*b_y + a_z*b_z
我正在尝试在程序集中制作一个执行上述计算的宏。
我的所有号码都使用 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
我认为这与我处理寄存器的方式有关。
我应该怎么做?
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. Also see other links to docs and guides in the 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
我不确定您的 "output" 函数/宏应该如何工作,但将整数存储到字节数组 BYTE 40 DUP (0)
似乎不太可能。您可以使用 mov dword ptr [answer], eax
,但也许您应该只使用 output eax
。或者,如果 output answer
将 eax 转换为存储在 answer
中的字符串,那么您不需要首先使用 mov
。
我假设您的号码是 signed 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 上(例如,英特尔 SnB 系列具有 imul r32, r32
3 个周期的延迟,其中 1每周期吞吐量)。标量版本有很多指令级并行性:加载和乘法是独立的,只有加法合并结果是相互依赖的。
DPPS 很慢(Skylake 上有 4 微指令和 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 个周期外,这里的所有内容都是最近 Intel 的一个周期延迟。 (MOVD 是 2 个周期,但您可以直接存储到内存。加载显然也有延迟,但它们来自固定地址,因此没有输入依赖性。)
这是我想要实现的目标:
a_x*b_x + a_y*b_y + a_z*b_z
我正在尝试在程序集中制作一个执行上述计算的宏。
我的所有号码都使用 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
我认为这与我处理寄存器的方式有关。
我应该怎么做?
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. Also see other links to docs and guides in the 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
我不确定您的 "output" 函数/宏应该如何工作,但将整数存储到字节数组 BYTE 40 DUP (0)
似乎不太可能。您可以使用 mov dword ptr [answer], eax
,但也许您应该只使用 output eax
。或者,如果 output answer
将 eax 转换为存储在 answer
中的字符串,那么您不需要首先使用 mov
。
我假设您的号码是 signed 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 上(例如,英特尔 SnB 系列具有 imul r32, r32
3 个周期的延迟,其中 1每周期吞吐量)。标量版本有很多指令级并行性:加载和乘法是独立的,只有加法合并结果是相互依赖的。
DPPS 很慢(Skylake 上有 4 微指令和 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 个周期外,这里的所有内容都是最近 Intel 的一个周期延迟。 (MOVD 是 2 个周期,但您可以直接存储到内存。加载显然也有延迟,但它们来自固定地址,因此没有输入依赖性。)