Mega Code Archive

 
Categories / Delphi / Examples
 

Why use Assembler

Title: Why use Assembler ? Question: What's behind Assemblercode and a general understanding of what is meant by terms such as instantiation, null pointer and register memory allocation. Answer: We have always found OP (ObjectPascal) to produce fast and more efficient code, add this to the RAD Environment of Delphi and Kylix, so therefore the need to use assembler becomes a question mark. The article is somewhat an extract of the excellent paper "Learning Assembler with Delphi" from Ian Hodger at: http://www.delphi3000.com/articles/article_2245.asp It was the wish of my students to shorten it and say a few words about debugging. At the end of the article, you'll find an impressive assembler example in a DOS-Shell, it's shows a graphic-fire on the screen. In all of our work with OP, we faced just five situations where we have felt one should consider the use of low level system code: 1. Stepping and processing large quantities of data. Of course I exclude from this any situation where a data query language with a query optimizer is employed, but not always in a automation- or micro-controller environment. 2. For scientific reason to provide high speed simulations or just for education with Compiler-Assembler-Linker-Loader steps. I call that CALL 3. For controller programming or to develop or test peripherals like COM-devices, e.g. on how to detect free COM ports (as far as Windows knows..) and call some functions Windows doesn't allow. I.e, does not detect a COM port if a mouse is attached. Uses a DPMI call with assembler 4. High speed display routines; here we want quick and easy routines that comes with OP, not the strange C++ headers, external function libraries or confused hardware demands of DirectX 5. Strong and fast encryption algorithm like ciphers, hashes or checksums, so the core encoding and decoding routines are written in highly optimized assembler code. To say that writing machine code is cosy would be an understatement, and as for debugging an assembler language is just an easy way of remembering what machine code operations are available. The job of converting to machine code is done by an assembler, so Borland's Turbo Assembler is built into Delphi. Let's practice a little "Bit": If we look at adding an integer 15 to the register eax, the appropriate assembler instruction is add eax,15 //a := a + 15 Almost the same, to subtract the value of ebx from eax sub eax,ebx //a := a - b To save a value for a happy day, we can move it to another register mov eax,ecx //a := c or even better, save the value to a memory address mov [1733],eax //store value of eax at address 1733 and of course to retrieve it with mov eax,[1733] This means also the the largest number we can store in a register, e.g. eax is 2 to the power 32 minus 1, or exactly 4294967295. Bear in mind the size of the values you are moving about; the mov [1733],eax instruction affects not only memory address 1733, but 1734,1735 and 1736 as well, because as you will recall eax is 32 bits long, or rather 4 bytes, therefore memory is always addressed in bytes! Next step is the example: step:= step + 1; we would get something like this: mov eax, step add eax, 1 mov step, eax cause at least one parameter of almost each instruction must be a register. Now wer'e ready for Our first snippet of assembler, but let aside the simple nature of this example. Consider the following lines of OP code: function BigSum(A, B: integer): integer; begin result := A+B; end; OP provides the asm .. end block as a method of introducing just plain assembler to our code of life. So we could rewrite the function BigSum function BigSum(A, B: integer): integer; begin asm mov eax,A add eax,B mov result,eax end; end; This works fine, but there is a point to consider. There is no speed gain and we've lost the readability of our code. But the fact is, all our sophisticated class design is "broken" bi an assembler. You can also write complete procedures and functions using inline assembler code, without including a begin...end statement, e.g: function LongMul(X, Y: Integer): Longint; asm mov eax, X imul Y end; The compiler performs several optimizations on these routines so no code is generated to copy value parameters into local variables. This affects all string-type value parameters and other value parameters whose size isnt 1, 2, or 4 bytes. Within the routine, such parameters must be treated as if they were var parameters. If we are going to produce really useful code, at some point we shall need to implement more deeper routines E.G. we have to display the output of some function dependant upon two variables. You might imagine this as a three-dimensional map, where the coordinates [X,Y] correspond to a height H. When we plot the point [X,Y] on the screen we need to give the imagination of depth. This can be achieved by using colours of differing intensity, blue below sea level and green above. What is needed is a function that will convert a given height into the appropriate depth of color for a given sea level. Debugging code ----------------------------------- To finish this article, let's say few words about debugging in order. It is very easy to set up watches, program break's, and traverse OP programs a line at a time. The same is true, even when using assembler. All we need to do is add the four 32bit general registers eax, ebx, ecx and edx to one's watch list, and see the effect of each line of assembler. In a DOS-Shell you can also type "debug" and then u for unassemble. The built-in assembler allows you to write Intel assembler code within OP programs. It implements a large subset of the syntax supported by Turbo Assembler and Microsofts Macro Assembler, including all 8086/8087 and 80386/80387 opcodes and all but a few of Turbo Assemblers expression operators. Assembler functions return their results as follows. - Ordinal values are returned in AL (8-bit values) - AX (16-bit values), or EAX (32-bit values). - Real values are returned in ST(0) on the coprocessors register stack. - Pointers, including long strings, are returned in EAX. - Short strings and all the variants are returned in the temp. location pointed to by @result. So I hope you feel a little "Bit" the speed of Delphi and thanks Ian for his fundamentals. Fire example -------------------------------- PROGRAM firefast; {$G+} USES CRT, DOS; const intensit=200; var buf: array[0..102,0..159] of integer; i: word; delta: integer; pal: array[0..255,1..3] of byte; PROCEDURE setpalette; var i: integer; (* 0..65'535 - in 2 Bytes abgespeichert *) begin for i:= 0 to 63 do begin pal[i,1]:=i; pal[i,2]:=0; pal[i,3]:=0; pal[i+64,1]:=63; pal[i+64,2]:=i; pal[i+64,3]:=0; pal[i+128,1]:=63; pal[i+128,2]:=63; pal[i+128,3]:=3; pal[i+192,1]:=63; pal[i+192,2]:=63; pal[i+192,3]:=63; end; asm; mov ax,seg pal; mov es,ax mov dx,offset pal; mov ax,$1012 mov bx,$0000; mov cx,$00FF; int $10; end; end; PROCEDURE interpolation; assembler; asm; mov cx,16159; mov di,offset buf; add di,320 @L1: mov ax,ds: [di-2]; add ax,ds: [di] add ax,ds: [di+2]; add ax,ds: [di+320] SHR ax,2 jz @L2; sub ax,1 @L2: mov word ptr ds: [di-320], ax add di,2; loop @L1; end; PROCEDURE purgebuf; assembler; asm; mov si,offset buf mov ax,$A000 mov es,ax; mov di,0; mov dx,100 @L3: mov bx,2 @L2: mov cx,160 @L1: mov al,[si] mov ah,al; mov es:[di],ax add di,2 add si,2;dec cx jnz @L1 sub si,320;dec bx jnz @L2 add si,320;dec dx jnz @L3; end; BEGIN randomize; inline($B8/$13/$00/ $CD/$10); setpalette; fillchar(buf,sizeof(buf),0); repeat interpolation; for i:=0 to 159 do begin if random delta:=random(2)*intensit; buf[101,i]:=delta; buf[102,i]:=delta; end; purgebuf; until keypressed; textmode(CO80); end.