5

I decided to learn an assembly programming language. I am using this 8086 tutorial. At the bottom the exercise is to find an error in some instructions and one of them is

mov cx, ch 

I found some similar question on SO on this topic explaining how to achieve it but now I'd like to know why this operation is forbidden?

Let's assume I have 10d = 00001010b in CH and want to put it to CL and simultaneously erase CH. mov cx, ch seems to do it because it shows 10d as 16bit 00000000 00001010 and puts it respectively to CH and CL (entire CX)

What is wrong with it and why does given tutorial ask to find an error in this expression?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Bartłomiej Szałach
  • 2,393
  • 3
  • 30
  • 50
  • 2
    It is not so much forbidden, more "not provided for in the [full list of all Intel opcodes](http://www.intel.nl/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-2a-manual.pdf) (link to PDF)". So yah, it is forbidden because the opcode does not exist. – Jongware Apr 18 '15 at 11:44
  • 1
    I highly suspect that a great deal of your confusion is the fact that the author has (probably deliberately) chosen the `CH`, the `CL`, and the `CX` registers for this question. To help you understand the discontinuity, you can get a highly similar error with `mov dx, ch` which, possibly, could illuminate the reason for the error a little better. Just a suggestion; money back if not satisfied. – User.1 Apr 19 '15 at 18:11
  • related: [MOV 8 bit to 16 bit register (al to bx)](//stackoverflow.com/a/58768040) is essentially a duplicate. – Peter Cordes Nov 18 '19 at 07:35

4 Answers4

8

The mov instruction is used to move between operands of the same size. What you want to is extend the 8-bit ch into the 16-bit cx. There are two instructions available for that purpose:

movzx cx,ch  ; zero-extends ch into cx. the upper byte of cx will be filled with zeroes
movsx cx,ch  ; sign-extends ch into cx. the upper byte of cx will be filled with the most significant bit of ch

Another way of accomplishing the same thing in this particular case would be:

shr cx,8  ; zero-extend
sar cx,8  ; sign-extend
Michael
  • 57,169
  • 9
  • 80
  • 125
  • 1
    Zero extension is achieved with `mov cl,ch; xor ch,ch`. Sign extension cannot be obtained via `sar cx,8` because shifting by more than one position requires the use of `cl` on the 8086. The solution is `mov cl,8; sar cx,cl`, which is rather slow on 8088 and 8086 processors. – chqrlie Oct 24 '19 at 14:08
  • @chqrlie: This was tagged [tag:masm32]. That implies a post-8086 dev environment at least, and nothing in the question implies developing code that's backwards compatible with 8086. That's mostly irrelevant these days unless targeting an embedded 8086 microcontroller (or doing homework with emu8086 I guess but performance doesn't matter there). – Peter Cordes Oct 24 '19 at 15:22
  • @PeterCordes: The question is actually tagged `masm` and the OP refers to this page: https://web.archive.org/web/20150318063331/http://www.csi.ucd.ie/staff/jcarthy/home/alp/alp1.html *Introduction to 8086 Programming*... Fiddling with 8- and 16-bit registers can still be done in 32- and 64-bit assembly, but more recent tutorials focus or more juicy stuff. – chqrlie Oct 24 '19 at 17:46
  • @chqrlie: I said "was", [before I retagged](https://stackoverflow.com/posts/29716796/revisions) to include x86 and zero-extension (based on the bit-pattern the OP mentioned) because the answer isn't specific to masm32, let alone masm. But fair enough, I didn't check out the link in the question body. – Peter Cordes Oct 24 '19 at 22:51
2

The problem is, that you're trying to move the contents of an 8 bit register ch into a 16 bit register cx. You can't do that because the registers are different sizes.

So I'd guess that you get an error message like "invalid combination of opcode and operands".

p.s: exchanged 8 and 16 above; the statement stays the same. Check for instance this overview. As you see, there is no combination of different register sizes defined. It means there doesn't exist any OPcode that represents mov cx, ch.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Trinimon
  • 13,839
  • 9
  • 44
  • 60
2

You want to move the contents of CH to CX on an 8086.

On more recent processors, such as the 80286, you could just shift the value of CX right by 8 positions, with or without sign replication:

; zero extend ch into cx
    shr cx,8

; sign extend ch into cx
    sar cx,8

These instructions are not available on the 8088 or the 8086. You can must use CL to specify the shift count:

; zero extend ch into cx
    mov cl,8
    shr cx,cl

; sign extend ch into cx
    mov cl,8
    sar cx,cl

Yet this method is very slow because the shift by a variable number of positions takes multiple cycles per position.

Here is a faster method:

; zero extend ch into cx
    mov cl,ch
    xor ch,ch

; sign extend ch into cx
    mov cl,ch
    neg ch     ; set the carry flag if ch is negative
    sbb ch,ch  ; set all bits if ch was negative, clear them otherwise

If you can destroy AX, you can save code size by using cbw which is designed for this. On original 8086 and especially 8088, small = fast because code fetch was a major bottleneck. That's not true on modern x86, though.

; sign extend ch into ax
    mov   al, ch
    cbw                 ; sign-extend AL into AX
; optionally move back to cx
    xchg  cx, ax        ; smaller than mov cx, ax

To avoid destroying AX, you could do mov cl,ch ; xchg ax,cx ; cbw and stop there, or do a final xchg ax,cx to just sign-extend CH into CX and restore everything else. xchg with AX is a 1-byte instruction, and so is cbw and cwd (extend AX into DX:AX, e.g. before 16-bit idiv)

cbw is exactly like 386 movsx ax, al.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    A better on 8086 where code-size is the major factor in performance: `mov cl,ch` ; `xchg ax,cx` ; `cbw` ; `xchg ax,cx`. This kind of thing (AX being special because of missing movsx, 2-operand imul, etc.) is why 8086 spends 8 opcodes to have single-byte short forms of `xchg ax, reg`. – Peter Cordes Oct 24 '19 at 15:16
  • 1
    Or if you can destroy AX, `mov al,ch` ; `cbw` ; `xchg ax,cx` – Peter Cordes Oct 24 '19 at 15:17
  • @PeterCordes: yes indeed! It looks like we come from the same playground :) – chqrlie Oct 24 '19 at 17:37
  • 1
    Doubtful; I've never actually run anything I've written on a real 8086, or even run in real mode at all. I just happen to know how to optimize for code-size for x86 in general, for fun and as a tie-breaker for performance. But stuff like this, for size over speed, mostly for [Tips for golfing in x86/x64 machine code](//codegolf.stackexchange.com/q/132981) and to understand historical ISA design decisions that burden current x86. But if you mean that we like to optimize for fun and profit, then yes :) – Peter Cordes Oct 24 '19 at 22:55
0

just do it with simple instructions

mov cl,ch  ; copy high bits to low
xor ch,ch  ; clear high-bits

it is common in 16bit programming and it only takes 2 clock cycles.

The use of the movezx/movsx needs 3 clock cycles. Use

movsx cx,ch

for moving byte to word with sign-extension and

movzx cx,ch

to Move byte to word with zero-extension

Thomas
  • 2,345
  • 1
  • 18
  • 17
  • On most modern x86 CPUs, `movzx`/`movsx` have 1 cycle latency, and are a single uop. For throughput purposes, that means they need 0.25 cycles on a typical 4-wide OoO exec CPU. However, reading from CH can add an extra cycle of latency on Haswell/Skylake. It makes no sense to talk about cost in cycles without specifying a microarchitecture. (And on out-of-order CPUs, "cost in cycles" isn't a thing; performance isn't a simple sum of 1-dimensional costs) – Peter Cordes Oct 24 '19 at 15:11