为什么8位MUL合并到AX中,而16位和32位MUL将结果拆分为[E]DX:[E]AX?
-
MUL CLCL 是 BYTE 大小,它等于AX = AL * CL -
MUL BXBX 是 WORD 大小,它等于DX:AX = AX * BX -
MUL EBXEBX 是 DWORD 大小,它等于EDX:EAX = EAX * EBX
我想知道为什么 8 位大小的MUL指令给出的结果是 inAX而不是 in DL:AL?
是因为对于 16 位 MUL,结果可以是EAX,对于 32 位 MUL 结果可以是RAX,但是对于 64 位mul rcx,不会有足够宽的单个寄存器来保存结果吗?
回答
8086 有 16 位寄存器(AX、BX、...)。因此,8*8=16 加宽乘法(即,具有 8 位输入和 16 位结果)可以将其结果放入单个寄存器中。将它拆分到两个寄存器会很不方便并且没有任何好处。
但是 16*16=32 加宽乘法无法将其结果放入单个寄存器中,因为没有 32 位寄存器。必须将它拆分到两个寄存器中,因此选择了 DX 和 AX。
同样,386 有 32 位寄存器(EAX、EBX、...),因此它的 32*32=64 加宽乘法必须将其结果拆分。选择 EDX:EAX 是因为与 8086 相似。
英特尔此时可以添加一个新版本的 16*16=32 MUL,将结果保留在单个 32 位寄存器中,例如 EAX,但他们选择不这样做,可能是为了兼容性或避免不必要的额外复杂性,或者从简单的惯性。因此MUL,即使在 32 位模式下,386 的 16*16=32仍会将其结果拆分为 DX:AX。
(然而,他们确实添加了一个非扩展的 32*32=32 形式的有符号乘法IMUL指令,将其结果留在一个 32 位寄存器中。可以将其用于有符号 16*16=32 乘法 -扩展输入,为此MOVSX也增加了便利。它可以用于 16*16=32 无符号乘法,通过零扩展输入,如果知道乘积将小于2^31。)
同样,x86-64 也有 64 位寄存器。对于现有的乘法指令,他们保持相同的行为(因此 32*32=64 仍然将其结果拆分到 EDX:EAX 而不是使用单个 64 位寄存器),并且他们添加了一个 64*64=128 加宽乘法,同样,必须拆分其结果,并将其留在 RDX:RAX 中。还有一个非加宽的 64*64=64 有符号IMUL,它将结果留在一个 64 位寄存器中。
- Note that `imul r64, r64` works totally fine with two 32-bit unsigned inputs that have been zero-extended to 64-bit. https://godbolt.org/z/xM8PT1 shows GCC and clang doing that for `a * (uint64_t)b`. The sign bits of the wider registers will always be clear, so they'll be treated as signed-positive (thus with the right values). The difference between signed and unsigned multiply in the upper half is only because of the interpretation of the MSB of the inputs. "signed overflow" of the product (MSB set) doesn't do anything (e.g. `0x00ff * 0x00ff = 0xfe01` with imul r16,r16) except FLAGS.