简体   繁体   中英

Converting Turbo Pascal inline code to Object Pascal

While converting old Turbo Pascal units to modern Object Pascal, I ran into the following:

function Less (var a, b; Relation : POINTER) : boolean;
    inline($5B/$59/$0E/$E8/$00/$00/$58/$05/$08/$00/$50/$51/$53/$CB);

The code is supposed to call an external function {$F+} function VariableLess(var a, b: Index): boolean; {$F-} external function {$F+} function VariableLess(var a, b: Index): boolean; {$F-} , collect the result and pass it to the calling function. The function is used in a unit that provides binary trees for untyped data

procedure InsVarBBTree(var B: BBTree; var E; S: word; A: pointer; var ok: boolean);
{ puts variable E of size S into tree B. The order relation address is A. }

Therefore, the unit itself cannot provide a comparison function, that is the job of the unit that defines the payload.

Using an online disassembler I found out that this corresponds to:

{$ASMMODE intel}
function Less (var a, b; Relation : POINTER) : boolean; assembler;

asm
  pop  bx
  pop  cx
  push cs
  call 6
  pop  ax
  add  ax, 8
  push ax
  push cx
  push bx
  retf
end;

However, the compiler doesn't like the push statement. What should I do to get this to work on a modern 64-bit machine? I realise the code is 16-bit.

I just compiled some inline function on Turbo Pascal 5 for MS-DOS to check how Turbo Pascal generates code:

For non- inline function calls, Turbo Pascal pushes all function arguments to the stack. The first one is pushed first (so SS:SP points to the last function argument). Then a ( far ) call is executed. The function returns using retf n , which means that the function called removes all parameters from the stack.

In an inline function, the raw bytes given simply replace the call instruction. This means that SS:SP points to the arguments, not to the return address. The inline machine language code must pop the arguments from the stack. And it must not return using ret but simply continue code execution at the instruction after the inline code.

With this knowledge the assembly code can be analyzed:

Using the assembly code given, you can call any function or procedure with any parameters (in your case: VariableLess ) indirectly by writing a helper function (in your case: Less ) that has the same arguments as the function to be called plus an additional argument that points to the actual function.

The code is equal to the following Delphi or FreePascal code:

type
    TMyCompare = function(var a, b) : boolean;

function Less (var a, b; Relation : TMyCompare) : boolean;
begin
    Less := Relation(a, b);
end;

If your compiler supports function pointers ( type TMyCompare = function... ), you could do it like this.

Or you could even replace all occurrences of Less(x,y,z) in your program by z(x,y) . This would even be more efficient.

Of course, the pointer to the function ( VariableLess ) should not have the type pointer but the type TMyCompare if you do it like this.

If your compiler does not support function pointers (as Turbo Pascal obviously did not), you might need assembly.

But in that case, different compilers will need different assembly code!

So not knowing internals of your compiler, it is not possible to translate the assembly code.

EDIT

I'm not sure how exactly your compiler works. However, maybe the following code works if my original code does not work:

function Less (var a, b; Relation : Pointer) : boolean;
type
    TMyCompare = function(var a, b) : boolean;
var
    Relation2 : TMyCompare;
begin
    Relation2 := TMyCompare(Relation);
    Less := Relation2(a, b);
end;

The solution is as follows: Inside the unit handling dynamic types define

 type
      TMyCompare = function(var a, b) : boolean;  
    
    function Less (var a, b; Relation : TMyCompare) : boolean;
    
    begin
      Result := Relation(a, b);
    end;

    procedure InsVarBBTree(var B: BBTree; var E; S: word; A: TMyCompare; 
          var ok: boolean);
    { puts variable E of size S into tree B. The order relation address is A. }

This is called from outside

{$F+} function VariableLess(var a, b : Index) : boolean; {$F-}
begin
...
end;

InsVarBBTree(Baum, TempStr, SizeOf(TempStr), TMyCompare(@VariableLess), OK)

Thanks to all who helped with that

Engelbert

Afaik it will work for Free Pascal 16-bits targets, but you are probably not interested in that. For non 16-bit targets, 16-bit code will need rewrite, the memory model and ABI is different. Your disassembly is symbolic and the constants don't really reflect the meaning of the source like an original assembler source (.asm) would.

This procedure is particularly hairy since it seems to make assumptions on the ABI, so it is inherently unportable.

Moreover, even if you would succeed, then the result would be suboptimal, since the object pascal compilers (Delphi, Free Pascal) are way more optimizing than TP ever was. Using external assembler for short procedures stunts the ability of the compiler to inline.

I think Peter Cordes is right, and this is a kind of thunk to the real functionality after the shown procedure. Nope, I think Martin is closer. It is a bit unlogical but because the compiler can't really inline (only dump an assembler block) the parameter calling remains the same, and must be undone without access to a stack frame/local variables. TP doesn't keep values in registers, so this is relatively safe.

You could try to disassemble that too, but the best is probably to simply try to formulate an pascal substitute for it from documentation, and forget about all the micro-optimizations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM