Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Delphi Assembler Function Conventions

Reference Material

Delphi Assembler Function Conventions

by  Glenn9999  Posted    (Edited  )
Introduction and Purpose

This is a document describing how Delphi passes procedure and function items in reference to the built-in assembler. There is no intention to teach assembler, though there will be some instructive things in the examples. However, the intention is solely to show the assembler programmer how to interact with the Delphi procedure and function headers.

Code & Examples

As one might be aware, there are a few methods that Delphi uses to pass procedure and function information. The default is register, which is what the examples will be using. The others use variables passed on the stack using various methods.

The examples will have numerous comments both to function as the reference for this FAQ, as well as a few instructive ASM comments. The examples should not be considered as good, efficient or completely bug-free ASM as well, but only simple ASM, so the interactions between Delphi and ASM might be clearly shown.

Example 1
Code:
{$APPTYPE CONSOLE}
program asmdoc1; uses sysutils;
{
  Delphi assembler doc example #1 - simple parameters, simple function result
  things like integers, char, pointer - anything 4 bytes or less which is not
  an FP type
}

function addthreeintegers1(num1, num2, num3: integer): integer; assembler;
{ num1 in EAX, num2 in EDX, num3 in ECX, result in EAX }
  asm
    ADD  EAX, EDX             // EAX := EAX + EDX;
    ADD  EAX, ECX             // EAX := EAX + ECX;
  end;

procedure addthreeintegers2(var num1: integer; num2, num3: integer); assembler;
{ num1 is a pointer to contents of data, put in EAX, num2 in EDX, num3 in ECX }
  asm
    ADD EDX, ECX                       // EDX := EDX + ECX;
    MOV ECX, [EAX]                     // ECX := DWord(EAX^);
    ADD EDX, ECX                       // EDX := EDX + ECX;
    MOV [EAX], EDX                     // DWord(EAX)^ := EDX;
  end;

var
  a: integer;
begin
  writeln('Tek-tips Delphi ASM FAQ example #1 - simple parameters');
  writeln;
  a := addthreeintegers1(2, 3, 6);
  writeln('Answer is ', a);
  addthreeintegers2(a, 2, 1);
  writeln('Answer is ', a);
  write('Press ENTER to exit.');
  readln;

Example 2
Code:
{$APPTYPE CONSOLE}
program asmdoc2; uses sysutils;
  { Delphi assembler doc example #2 - ShortStrings }

function AChangeChar(instr: ShortString): ShortString; assembler;
{  increments each ASCII character by 1 and returns the string

  EAX is pointer to string block represented by instr,
  output function result is in EDX as a pointer,
  String function results are pushed as an additional VAR parameter
}
  asm
    // Delphi procedures/functions must preserve EBX, ESI, and EDI if used
    PUSH   EBX
    // first byte of ShortString is a length.  Copy that to result
    MOV    CL, Byte [EAX]          // CL := Byte(EAX^);
    MOV    Byte [EDX], CL          // Byte(EDX^) := CL;
    // now process the string
    @Loop1:                        // for i := CL downto 0 do
    CMP    CL, 0
    JE     @end
    MOV    BL, Byte [EAX+1]        // BL := Byte((EAX+1)^);
    ADD    BL, 1                   // BL := BL + 1;
    MOV    Byte [EDX+1], BL        // Byte((EDX+1)^) := BL;
    INC    EAX                     // EAX := EAX + 1;
    INC    EDX                     // EDX := EDX + 1;
    DEC    CL                      // CL := CL - 1;
    JMP    @loop1
    @end:
    POP    EBX
  end;

var
  a: ShortString;
  b: ShortString;
begin
  writeln('Tek-tips Delphi ASM FAQ example #2 - ShortStrings');
  writeln;
  a := 'ABCDEFGHIJK';
  writeln('Before the string is: ', a);
  b := AChangeChar(A);
  writeln(' After the string is: ', b);   // answer should be 'BCDEFGHIJKL'
  write('Press ENTER to exit.');
  readln;
end.

Example #3
Code:
{$APPTYPE CONSOLE}
program asmdoc3; uses sysutils;
  { Delphi assembler doc example #3 - var AnsiStrings
    applies to WideStrings as well }

procedure AFillCharString(var instr: AnsiString; fchar: AnsiChar); assembler;
  {
  fills string with character "fchar"

  instr is address in EAX
  fchar is in EDX (specifically DL) as pointer to the AnsiString
  }
  asm
    MOV   EAX, [EAX]        // get contents of string
    CMP   EAX, 0            // check whether the string is nil
    JE    @exit             // exit if string is nil
// AnsiStrings are passed as pointers to the DATA, so length is before
    MOV   ECX, [EAX-4]
    @loop1:
    CMP   ECX, 0            // for i := ECX downto 1 do
    JE    @exit
    MOV   Byte [EAX], DL    // Byte(EAX^) := DL;
    DEC   ECX               // ECX := ECX - 1;
    INC   EAX               // EAX := EAX + 1;
    JMP   @loop1
    @exit:
  end;

var
  a: AnsiString;
begin
  writeln('Tek-tips Delphi ASM FAQ example #3 - AnsiStrings');
  writeln;
  a := 'ABCDEFGHIJK';
  writeln('Before the string is: ', a);
  AFillCharString(a, '+');
  writeln(' After the string is: ', a);
  write('Press ENTER to exit.');
  readln;
end.

Example #4
Code:
{$APPTYPE CONSOLE}
program asmdoc4; uses sysutils;
  { Delphi assembler doc example #4 - record types }
type
  MyRec = record
    x: integer;
    y: char;
  end;

procedure ARecordHandle1(var InRecord: myRec); assembler;
  { add 10 to InRecord.X and increment InRecord.Y by 1
    Record passed in EAX as address,
    using record typecasting
  }
  asm
    MOV EDX, MyRec([EAX]).x       // EDX := EAX^.x;
    ADD EDX, 10                   // EDX := EDX + 10;
    MOV MyRec([EAX]).x, EDX       // EAX^.X := EDX;
    MOV DL, MyRec([EAX]).y        // DL := EAX^.y;
    INC DL                        // DL := DL + 1;
    MOV MyRec([EAX]).y, DL        // EAX^.y := DL;
  end;

function ARecordHandle2(InRecord: myRec): MyRec; assembler;
  { add 10 to InRecord.X and increment InRecord.Y by 1
    Record passed in EAX as address always and can be changed always
    Record address for output in EDX
    using memory positioning, watch value alignment on records
    if you do it this way
  }
  asm
    MOV ECX, [EAX]                // ECX := DWord(EAX^);
    ADD ECX, 10                   // ECX := EDX + 10;
    MOV [EDX], ECX                // EDX^ := ECX;
    MOV CL, [EAX+4]               // DL := Byte((EAX+4)^);
    INC CL                        // DL := DL + 1;
    MOV [EDX+4], CL               // Byte((EAX+4)^) := DL;
  end;

var
  a, b: MyRec;

begin
  writeln('Tek-tips Delphi ASM FAQ example #4 - record types');
  writeln;
  A.x := 30;
  A.y := 'C';
  ARecordHandle1(a);
  writeln('A.x is : ', A.x);
  writeln('A.y is : ', A.y);
  b := ARecordHandle2(a);
  writeln('B.x is : ', B.x);
  writeln('B.y is : ', B.y);
  write('Press ENTER to exit.');
  readln;
end.

Example #5
Code:
{$APPTYPE CONSOLE}
program asmdoc5; uses sysutils;
  { Delphi assembler doc example #5 - FP types }

function AFPAddTwoNumbers1(In1, in2: Double): Double; assembler;
  { add FP numbers in1 and in2, return as result
    FP numbers are always passed on the stack in blocks divisible by 4,
    so extended always takes up 12 bytes, lower
    FP numbers returned in ST(0) for functions
   }
  asm
    FLD      Double(SS:[ESP+8])         // Load in2 on FP stack, can use alias
    FLD      Double(SS:[ESP+16])        // Load in1 on FP stack, can use alias
    FADD     ST(0), ST(1)               // ST(0) := ST(0) + ST(1);
  end;

procedure AFPAddTwoNumbers2(var In1: Double; in2: Double); assembler;
  { add FP numbers in1 and in2, return result in in1
    var value is a pointer to memory data.
   }
  asm
    FLD      Double(SS:[ESP+8])         // Load in2 on FP stack, can use alias
    FLD      Double([EAX])              // Load in1 contents on FP stack
    FADD     ST(0), ST(1)               // ST(0) := ST(0) + ST(1);
    FSTP     Double([EAX])              // Store result back to in1 contents
  end;

var
  a, b, c: Double;
begin
  writeln('Tek-tips Delphi ASM FAQ example #6');
  writeln;
  a := 1.1;
  b := 2.2;
  c := AFPAddTwoNumbers1(a, b);
  writeln('C is: ', c);
  AFPAddTwoNumbers2(c, b);
  writeln('C is: ', c);
  write('Press ENTER to exit.');
  readln;
end.

Example #6
Code:
{$APPTYPE CONSOLE}
program asmdoc6; uses sysutils;
  { Delphi assembler doc example #6 - Complex example #1 }

function AAverageFiveInts(in1, in2, in3, in4, in5: integer): Extended; assembler;
  { in1 in EAX, in2 in EDX, in3 in ECX, in4 & in5 are on stack.
    Extended value returned on FP stack }
  var
    Res1: integer;
  asm
    ADD    EAX, SS:[ESP+12]      // add in4 to in1, can use alias
    ADD    EAX, SS:[ESP+16]      // add in5 to in1, can use alias
    ADD    EAX, ECX              // add in3 to in1
    ADD    EAX, EDX              // add in2 to in1
{ you can not directly interact with the FPU with the CPU registers, so the
  memory store is a requirement }
    MOV    Res1, 5               // move 5 to local memory
    FILD   Res1                  // load integer from memory
    MOV    Res1, EAX             // move EAX to memory
    FILD   Res1                  // load memory to register
    FDIV   ST(0), ST(1)          // f-point division by 5
  end;

var
  a: Extended;
begin
  writeln('Tek-tips Delphi ASM FAQ example #6 - Complex Example #1');
  writeln;

  a := AAverageFiveInts(1, 2, 3, 4, 7);
  writeln('Answer is ', a:0:20);
  write('Press ENTER to exit.');
  readln;
end.

Example #7
Code:
{$APPTYPE CONSOLE}
program asmdoc7; uses sysutils;
  { Delphi assembler doc example #7 - Complex example #2 }

procedure ASomeRandomStuff(in1: extended; in2: integer; var in3: extended;
                          var in4: char; in5: word); assembler;
  { does some random junk stuff, shows how something like this procedure
  header is handled in ASM
    1) adds in1, in2, in3, and in5, stores result in in3.
    2) increments in4 by 2.
  in1 = comes from stack   in2 = EAX   in3 = address on EDX
  in4 = address on ECX     in5 = on stack
  }
  var
    Res1: integer;
  asm
    ADD    EAX, DWord(in5)        // in2 := in2 + in5;
    FLD    Extended([EDX])        // load in3
    FLD    in1
    FADD   ST(0), ST(1)
    MOV    Res1, EAX
    FILD   Res1
    FADD   ST(0), ST(1)
    FSTP   Extended([EDX])
// work on the character part
    MOV    AL, Byte [ECX]
    ADD    AL, 2
    MOV    Byte[ECX], AL
  end;

var
  a: Extended;
  b: char;
begin
  writeln('Tek-tips Delphi ASM FAQ example #7 - Complex Example #2');
  writeln;
  a := 3.14;
  b := 'C';
  ASomeRandomStuff(0.2, 20, a, b, 12);
  writeln('A is: ', a:0:20);
  writeln('B is: ', b);
  write('Press ENTER to exit.');
  readln;
end.
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top