4

So I am using C++ with assembly to do 16-bit signed multiplication.

I know that for 16-bits, the multiplicand is AX and the multiplier is either a register or memory operand 16 bit with the product being stored in EDX:EAX which is twice the size of operands.

I am a beginner so I tried first with 8 bit signed multiplication in which it works:

#include "stdafx.h"
#include <stdio.h>

int main()
{
    char X, Y;
    short Z;
    _asm
    {
        MOV X, 5
        MOV Y, 11

        MOV AL, X
        MOV BL, Y
        IMUL BL

        MOV Z, AX

    }

    printf("The result times is = %i", Z);



    getchar();
    //system("pause");
    return 0;
}

but I'm unsure why the following code wouldn't work for 16 bit instead.

#include "stdafx.h"
#include <stdio.h>

int main()
{
    short X, Y;
    int Z;
    _asm
    {
            MOV X, 5
            MOV Y, 11

            MOV AX, X
            MOV BX, Y
            IMUL BX

            MOV Z, [DX::AX]

    }

    printf("The result times is = %i", Z);



    getchar();
    //system("pause");
    return 0;
}
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
user5894146
  • 91
  • 3
  • 6
  • 3
    I'm not sure you are doing this with a specific purpose in mind or playing around. `MOV Z, [DX::AX]` Isn't valid _MASM_ syntax. If you are sure you want to do a signed multiply of a 16-bit register by a 16-bit register and have result in DX:AX then you will have to replace `MOV Z, [DX::AX]` with something like `MOV word ptr [Z], AX` `MOV word ptr [Z+2], DX` . I'm assuming `int` is a 32-bit signed integer. You have to manually move AX and DX into the variable Z manually. If you had 32-bit registers available to you then you could simplify the code a bit. – Michael Petch Mar 14 '16 at 20:34
  • I want to basically only use 16 bit integers as x and y and multiply them. The result which will be stored in DX:AX; I want stored in Z which will be read by printf and output value of multiplication. I understand where I went wrong, I had the improper syntax. Thank you for your help, the textbook I was referring to didn't mention use of word ptr and was stuck I knowing I needed that. Once again, thanks for your help. – user5894146 Mar 14 '16 at 22:05
  • 1
    `MOV word ptr [Z], AX` moves the 16-bit value in AX and stores it into the 16-bit word at memory address of Z. Because x86 is Little Endian you have to store the lower 16-bits (AX) in the first 2 bytes of _Z_. `MOV word ptr [Z+2], DX` then stores the upper 16-bits (from _DX_) into the 16-bit word at memory address of Z+2 (the upper bits of a 32-bit integer value). – Michael Petch Mar 14 '16 at 22:09
  • 1
    That makes more sense now! I knew it had to involve little endian. Once again I appreciate your help. – user5894146 Mar 14 '16 at 22:21
  • 1
    Isn't stdafx.h a header for winform applications? How can it contain 16-bit code? Why don't use 32-bit code right now – phuclv Mar 15 '16 at 02:06

1 Answers1

1

The reason it doesn't work is that the instruction MOV Z, [DX::AX] does not exist.

You are correct that the result will be stored in DX:AX, but to store the result of those two registers in a memory location you'll have to do two stores.

lea ecx,z        //load the address of z into cx
mov [ecx],ax     //8086 is little endian, so store the lower bytes first.
mov [ecx+2],dx   //store the high bytes 

I must say that I'm appalled to see 16-bit assembly. The 386 was introduced in 1985, so you are about 30 years behind the times.

The way to multiply two 16 bit values is:

movsx eax, word ptr x        //sign extension prevents partial register writes
movsx edx, word ptr y        //use movzx if you want to do an unsigned mul
imul eax,edx                 //multiply, but only store result in EAX.
mov z,eax                    //save result in one go.

Use of 16 bit registers in x86 leads to very slow code, because the processor is not optimized for this mode of operation. Sometimes 16 bit code can be many times slower than normal code.
The below code will run much, much faster than the above.

  1. It does not suffer from stalls due to partial register writes.
  2. It does not perform operations on partial registers.
  3. It does not use two writes (one of which is unaligned) where a single write would suffice.

You also don't have to be aware of the subtleties of little endian in the last example.
You should avoid using the 16-bit registers at all costs.
The equivalent 32-bit code is almost always much faster.
The only exception if writing 16-bit values to memory if you are unable to combine two 16-bit writes into one 32-bit.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • 16-bit is dumb, and not a good way to learn x86, but it's not *that* slow on modern CPUs. Intel CPUs still have a decode penalty for the operand-size prefix on instructions with immediates (other than imm8) for instructions other than `mov` (e.g. `add r/m16, imm16` but not `add r/m16, imm8` in 32 or 64-bit mode, or for add r/m32, imm32 in 16-bit mode). But Intel CPUs (P6 and SnB family) rename partial registers to avoid false dependencies, and Haswell removes the penalty for merging them when reading a wider register after writing partial regs. – Peter Cordes Aug 26 '16 at 22:00
  • Anyway, yes I agree with the overall sentiment, especially [learning the DOS system-call API / ABI at the same time](http://stackoverflow.com/a/34918617/224132) as learning 16-bit with segmentation at the same time just makes it all harder to learn. And 32-bit ops are often usable [even without zeroing the upper 16b](http://stackoverflow.com/questions/34377711/which-2s-complement-integer-operations-can-be-used-without-zeroing-high-bits-in). But there are rare use-cases, and 16-bit ops don't usually destroy performance. – Peter Cordes Aug 26 '16 at 22:03
  • 1
    It boggles my mind that many university students are forced to learn 8086 asm for their classes (usually in emu8086, without the 386 conveniences like movsx). This is the source of many of the bad 16-bit questions on SO that clog up the x86 tag. – Peter Cordes Aug 26 '16 at 22:06