I made a simple assembly program to evaluate the function
$$f =\frac{a^2 + b(2c-d)^2}{3e}$$
Since this is basically my first ever, I would like to hear what I can improve and know how to use registers better.
.model small
.stack 100h
.data
a dw 4
b dw 15
c dw 86
d dw 155
e dw 8
res dw 0;
.code
mov ax,@data
mov ds,ax
mov dx,0
mov bx,0
mov cx,0
mov ax,c
shl ax,1
mov bx,d
sub ax,bx
mul ax
mul b
mov dx,ax
push dx
mov dx,0
mov ax,a
mul ax
pop cx
add ax,cx
mov dx,ax
push dx
mov dx,0
mov ax,e
mov bx,3
mul bx
mov bx,ax
pop cx
mov ax,cx
mov cx,bx
div cx
mov ax,4c00h
int 21h
end
2 Answers 2
Some registers have a basic purpose and they supposed to be used for those but it's not that you can't use cx
for storing arithmetic operation result. There are few things you could improve in your code
Comment
I would really recommend you to start putting comments. Assembly is not a difficult language but it's very context specific. Each similar looking assembly line might be doing different thing depending on the context and comments are important, even in such small application codebase as this one. Do not try to write what the instruction does in english like
add ax,cx ;adds content of cx to ax
but rather explain the meaning.
add ax,cx ;after that ax = a^2 + b(2*c - d)^2
Assembly
As I wrote above there's nothing wrong in your usage of assembly (unless you use some register that can't be used - like segment ones), but there are few things you could optimize.
mov ax,0
could be written as xor ax,ax
and saved few bytes (only if you want to). And you don't have to zero ax
& cx
as you don't use it before you assign any value to it.
This is a strange construct too:
mov dx,ax
push dx
mov dx,0
You are moving the result from calculation to dx
to push in to the stack and then clearing the register. Why not just
push ax
Instructions at the end also looks like they are doing too much of moving around so this could be simplified as:
mov ax,e
mov bx,3
mul bx
mov bx,ax
pop ax
div bx
You are writing that you want to use registers more so those push
/pop
could also be removed. Also res
is unsued in the code - remove it.
Final program (probably - could be even more improved)
.model small
.stack 100h
.data
a dw 4
b dw 15
c dw 86
d dw 155
e dw 8
.code
_start:
mov ax,@data
mov ds,ax
xor dx,dx
mov ax,c
shl ax,1 ; ax = 2*c
mov bx,d
sub ax,bx ; ax = 2*c - d
mul ax ; ax = (2*c - d)^2
mul b ; ax = b*(2*c - d)^2
mov cx,ax
xor dx,dx
mov ax,e
mov bx,3
mul bx ; ax = 3*e
mov bx,ax
mov ax,a
mul ax ; ax = a^2
add ax,cx ; cx = a^2 + b*(2*c - d)^2
div bx ; ax = (a^2 + b*(2*c - d)^2)/3e
mov ax,4c00h
int 21h
end _start
-
\$\begingroup\$ While these are litigious times, I don't believe OP was planning on 'suing' res. It might also be worth mentioning that there is no overflow checking here. If
c
were (say) 32768, theshl
might not give the result you were expecting. \$\endgroup\$David Wohlferd– David Wohlferd2018年03月10日 07:15:44 +00:00Commented Mar 10, 2018 at 7:15
First some observations about your code
mov dx,0 mov bx,0 mov cx,0
It's practically never needed to zero the registers prior to using them. And if ever it would be useful to wipe a register clean then xor
-ing that register with itself will produce the same result more efficiently. e.g. xor bx, bx
mov bx,d sub ax,bx
There's little point in first moving the contents of the variable d to a register and then doing a subtraction between registers when there's a possibility to subtract the variable directly from the accumulator writing sub ax, d
.
mov dx,ax push dx mov dx,0 mov ax,a mul ax
Since your intent is to put the value in AX
on the stack, do so in one go with push ax
.
And clearing the DX
register right before a mul
instruction is wasteful since
DX
is not among the inputs for the multiplicationDX
receives the high word of the 32-bit resulting product anyway
You can apply this several times in your program.
pop cx mov ax,cx mov cx,bx div cx
Since your intent is to put the value on the stack in AX
, do so in one go with pop ax
.
And moving the BX
register to the CX
register before the div
instruction is wasteful since the division can simply operate on BX
directly.
Next some improvements you can apply
Instead of
push
-ing /pop
-ing the result from b(2c-d)^2, you could move it directly toCX
. This shaves off an instruction.Instead of calculating the value of 3e with a multiplication that uses
AX
and thus requires you topush
/pop
the accumulator's pre-existing content, you could evaluate 3e with as little as 3 instructions:mov bx, e ; bx = 1e shl bx, 1 ; bx = 2e add bx, e ; bx = 3e
This shaves off 3 instructions.
Prior to the division operation you should zero the
DX
register but since the last multiplication before thisdiv
instruction leavesDX=0
it counts as an optimization to not writexor dx, dx
here. I've commented it out in below code!The single most important improvement is that you start writing comments that explain what the instructions in your program accomplish.
Applying all the above
mov ax, @data
mov ds, ax
mov ax, c ; ax = c
shl ax, 1 ; ax = 2c
sub ax, d ; ax = 2c - d
mul ax ; ax = (2c - d)^2
mul b ; ax = b(2c - d)^2
mov cx, ax ; cx = b(2c - d)^2
mov ax, a ; ax = a
mul ax ; ax = a^2
add ax, cx ; ax = a^2 + b(2c - d)^2
mov bx, e ; bx = e
shl bx, 1 ; bx = 2e
add bx, e ; bx = 3e
;;; xor dx, dx Previous MUL made DX=0
div bx ; ax = (a^2 + b(2c - d)^2) / 3e
mov ax, 4C00h ; DOS.TerminateWithExitcode
int 21h
To enhance the readability of your programs you should never be afraid to use lots of whitespace.
Check out imul
The program that you wrote only uses 8086 instructions. Perhaps this in intentional.
But if you're interested, x86 has a very powerful imul
instruction that
- allows to multiply by an immediate
- is no longer restricted to just the accumulator.
This is how the above code would look like:
mov ax, @data
mov ds, ax
imul cx, c, 2 ; cx = 2c
sub cx, d ; cx = 2c - d
imul cx, cx ; cx = (2c - d)^2
imul cx, b ; cx = b(2c - d)^2
mov ax, a ; ax = a
mul ax ; ax = a^2
add ax, cx ; ax = a^2 + b(2c - d)^2
imul bx, e, 3 ; bx = 3e
;;; xor dx, dx Previous MUL made DX=0
div bx ; ax = (a^2 + b(2c - d)^2) / 3e
mov ax, 4C00h ; DOS.TerminateWithExitcode
int 21h
Especially calculating 3e is now extremely simple!
What about overflow?
Finally, because of the exact test data (a=4, b=15, c=86, d=155, e=8) that your program uses, there's never any risk for overflow on these calculations.
In a realistic program however (e.g. one that uses user-inputted numbers) you should always check for overflow. Consult the manual to find out about when the arithmetic operations produce overflow.
-
\$\begingroup\$ Thanks you for your answer! I've just learned Assembly for 2 day and do that task. I think I need to check for overflow. \$\endgroup\$T.Phuoc– T.Phuoc2018年03月14日 12:47:12 +00:00Commented Mar 14, 2018 at 12:47
e == 0
? If so, what should happen then? \$\endgroup\$