【问题标题】:"invalid instruction operands" on mov ah, word_variable, and using imul on 16-bit numbersmov ah、word_variable 上的“无效指令操作数”以及在 16 位数字上使用 imul
【发布时间】:2016-09-19 20:11:06
【问题描述】:

这是我想要实现的目标: a_x*b_x + a_y*b_y + a_z*b_z

我正在尝试在汇编中创建一个执行上述计算的 MACRO。

我的所有号码都使用WORDs。这是我的代码:

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

我认为这与我处理寄存器的方式有关。

我应该怎么做呢?

【问题讨论】:

  • MASM 不会让您将字加载到字节寄存器中(如 al 或 ah)。
  • 那么将我的WORDs 加载到寄存器中并将它们相乘的正确方法是什么?
  • 顺便说一句,a cross product 与点积不同。您在宏中的评论甚至说“点积”。你应该重命名你的宏。
  • 我解决了这个问题。谢谢。对计算部分有帮助吗?
  • 您的输入是否必须为 WORD 大小?或者你真的需要做 16b * 16b => 32b (因为你使用的是imul ax instead of imul byte ptr [B_X])。实际上,当您执行imul ax 时,您正在对一个 16 位数字进行平方,在 DX:AX 中产生结果,我确定这不是您想要的。

标签: assembly macros x86 masm


【解决方案1】:

MOV 的两个操作数的大小必须相同。 AL 和 AH 是字节寄存器。

MASM 风格的汇编器根据您在符号名称后使用的DW 推断内存位置的大小。这就是它抱怨操作数大小不匹配的原因(带有通用的无用错误消息,也适用于许多其他问题)。

如果您真的想将 A_X 的第一个字节加载到 AL 中,您可以使用覆盖:mov al, BTYE PTR A_X


但这不是您想要的,因为您确实想要加载 16 位数字。两个 16 位数字的乘积最多可达 32 位(例如 0xffff^2 是 0xfffe0001)。所以只做 32 位数学可能是个好主意。

您还错误地使用了imulimul ax 设置了DX:AX = AX * AX(在一对寄存器中产生 32 位结果)。要将 AH * AL 相乘并在 AX 中得到结果,您应该使用 imul ah。请参阅insn ref manual entry for IMUL。另请参阅 标签 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 上(例如,英特尔 SnB 系列的 imul r32, r32 延迟为 3 个周期,每个周期吞吐量为 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 已过时,Skylake has lower throughputpmaddwd mm, mm 相比,pmaddwd xmm,xmm(或 256b ymm)已过时。

在最近的 Intel 上,这里的所有内容都是一个周期的延迟,除了 PMADDWD 的 5 个周期。 (MOVD 是 2 个周期,但您可以直接存储到内存中。加载显然也有延迟,但它们来自固定地址,因此没有输入依赖性。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 2020-09-12
    • 1970-01-01
    • 2014-04-15
    相关资源
    最近更新 更多