paxCompiler/Sources/PAXCOMP_ARM.pas
Dalibor Marković 9d0de424e8
Init
Signed-off-by: Dalibor Marković <dalibor31@gmail.com>
2024-07-06 22:28:12 +02:00

466 lines
12 KiB
ObjectPascal

// The code below is based on RTTI.pas Delphi XE5.
// Added extra parameter ByRefs.
unit PAXCOMP_ARM;
interface
uses
TypInfo, RTTI;
function Invoke(CodeAddress: Pointer;
const Args: TArray<TValue>;
const ByRefs: TArray<Boolean>;
CallingConvention: TCallConv;
AResultType: PTypeInfo;
IsStatic: Boolean;
IsConstructor: Boolean): TValue;
implementation
function UseResultPointer(TypeInfo: PTypeInfo; IsConstructor: Boolean): Boolean;
begin
if TypeInfo = nil then
Exit(False);
case TypeInfo^.Kind of
{$IFDEF AUTOREFCOUNT}
tkClass:
Result := not IsConstructor;
{$ENDIF AUTOREFCOUNT}
tkInterface, tkMethod, tkDynArray, tkUString, tkLString, tkWString,
tkString, tkVariant:
Exit(True);
tkRecord:
case GetTypeData(TypeInfo)^.RecSize of
{$IF Defined(CPUX64)}
1, 2, 4: Result := False;
8: Result := IsManaged(TypeInfo);
{$ELSEIF Defined(CPUX86)}
1, 2: Result := False;
4: Result := IsManaged(TypeInfo);
{$ELSEIF Defined(CPUARM)}
1: Result := False;
{$ENDIF CPU}
else
Result := True;
end;
tkArray:
{$IF Defined(CPUX64)}
Result := not (GetTypeData(TypeInfo)^.ArrayData.Size in [1, 2, 4, 8]);
{$ELSEIF Defined(CPUX86) or Defined(CPUARM)}
Result := not (GetTypeData(TypeInfo)^.ArrayData.Size in [1, 2, 4]);
{$ENDIF CPU}
else
Result := False;
end;
end;
function AllocReg(var Regs: UInt32): UInt32;
var
newRegs: UInt32;
begin
if Regs = 0 then
Exit(0);
newRegs := Regs and (Regs - 1); // clear lowest bit
Result := Regs and not newRegs; // reveal bit cleared
Regs := newRegs;
end;
function Align4(Value: Integer): Integer;
begin
Result := (Value + 3) and not 3;
end;
function PassByRef(TypeInfo: PTypeInfo; CC: TCallConv; IsConst: Boolean = False): Boolean;
begin
if TypeInfo = nil then
Exit(False);
case TypeInfo^.Kind of
tkArray:
Result := GetTypeData(TypeInfo)^.ArrayData.Size > SizeOf(Pointer);
{$IF Defined(CPUX86)}
tkRecord:
if (CC in [ccCdecl, ccStdCall, ccSafeCall]) and not IsConst then
Result := False
else
Result := GetTypeData(TypeInfo)^.RecSize > SizeOf(Pointer);
tkVariant: // like tkRecord, but hard-coded size
Result := IsConst or not (CC in [ccCdecl, ccStdCall, ccSafeCall]);
{$ELSEIF Defined(CPUX64)}
tkRecord:
Result := not (GetTypeData(TypeInfo)^.RecSize in [1,2,4,8]);
tkMethod,
tkVariant:
Result := True;
{$ELSEIF Defined(CPUARM)}
tkRecord:
Result := (CC = ccReg);
tkMethod,
tkVariant:
Result := True;
{$ENDIF CPUTYPE}
{$IFNDEF NEXTGEN}
tkString:
Result := GetTypeData(TypeInfo)^.MaxLength > SizeOf(Pointer);
{$ENDIF !NEXTGEN}
else
Result := False;
end;
end;
type
PParamBlock = ^TParamBlock;
TParamBlock = record
RegCR : array[0..3] of Int32;
StackData: PByte;
StackDataSize: Integer;
case Integer of
0: ( RegD: array[0..7] of Double );
1: ( RegS: array[0..15] of Single );
end;
procedure RawInvoke(CodeAddress: Pointer; ParamBlock: PParamBlock);
external 'librtlhelper.a' name 'rtti_raw_invoke';
function Invoke(CodeAddress: Pointer;
const Args: TArray<TValue>;
const ByRefs: TArray<Boolean>;
CallingConvention: TCallConv;
AResultType: PTypeInfo;
IsStatic: Boolean;
IsConstructor: Boolean): TValue;
function CalcStackSize: Integer;
var
i: Integer;
FreeCR,
FreeVFP: Integer;
begin
// Estimate maximum stack usage, assuming everything goes
// on the stack with 4-byte alignment.
Result := SizeOf(Pointer); // for potential managed return-value
// FreeCR := 4; // Number of core registers. R0 - R3
FreeCR := 0; // Number of core registers. R0 - R3
FreeVFP := 0; // Number of VFP(FP) registers. Default is 0
{$IFDEF ANDROID}
if CallingConvention in [ccReg] then
FreeVFP := 8; // D0-D7
{$ENDIF ANDROID}
for i := 0 to Length(Args) - 1 do
if PassByRef(Args[i].TypeInfo, CallingConvention) or ByRefs[i] then
begin
if FreeCR > 0 then
Dec(FreeCR) // use core register.
else
Inc(Result, SizeOf(Pointer))
end
else
begin
if Args[i].Kind = tkFloat then
begin
if FreeVFP > 0 then // use VFP register.
Dec(FreeVFP)
else
Inc(Result, SizeOf(Double));
end
else if Args[i].Kind = tkInt64 then
begin
if FreeCR >= 2 then // use 2 core registers.
Dec(FreeCR, 2)
else if FreeCR = 1 then // use last register and stack
begin
FreeCR := 0;
Inc(Result, SizeOf(Int32));
end
else
Inc(Result, SizeOf(Int64));
end
else
begin
if (Args[i].DataSize <= 4) and (FreeCR > 0) then
Dec(FreeCR)
else
Inc(Result, Align4(Args[i].DataSize));
end;
end;
end;
const
regNone = $00;
regCRAll = $0F;
regFPRAll = $FFFF;
var
stackData: array of byte;
block: TParamBlock;
top: PByte;
freeCRegs: UInt32; // 4-core registers (32bit)
freeFPRegs: UInt32; // 16-Single VFP registers (32bit)
// 8-Double VFP registers (64bit)
src: PByte;
// If RegFlag doesn't have any bit, -1 is returned.
function RegFlagToIndex(RegFlag: UInt32): Integer;
begin
Result := -1;
while (RegFlag <> 0) do
begin
inc(Result);
RegFlag := RegFlag shr 1;
end;
end;
function RegDoubleFlagToIndex(RegFlag: UInt32): Integer;
begin
Result := -1;
while (RegFlag <> 0) do
begin
inc(Result);
RegFlag := RegFlag shr 2;
end;
end;
function AllocDoubleReg: UInt32;
var
freeDoubleReg: Uint32;
begin
Result := 0;
freeDoubleReg := freeFPRegs and $55555555; // remove odd FP registers.
// no free Double register.
if freeDoubleReg = 0 then Exit;
// get a free single register at even index.
Result := not(freeDoubleReg and (freeDoubleReg - 1)) and freeDoubleReg;
// Remove two Single registers from FreeFPRegs;
freeFPRegs := freeFPRegs and not( Result or Result shl 1);
end;
{$IF not defined(IOS)}
function AllocEvenReg(var Regs: UInt32): UInt32;
begin
Result := AllocReg(Regs);
// If get a odd reg, alloc a reg again
// 0002 - R1
// 0008 - R3
if (Result and ($2 + $8) <> 0) then
Result := AllocReg(Regs);
end;
{$ENDIF !IOS}
procedure PutArg(const Arg: TValue);
var
dataSize: Integer;
reg,
regL, regH: UInt32;
L32, H32: UInt32;
U64: UInt64;
begin
dataSize := Arg.DataSize;
if (Arg.Kind = tkFloat) and (Arg.TypeData.FloatType in [ftSingle, ftDouble, ftExtended]) then
begin
if dataSize = 4 then // Single
begin
// First, allocate one single VFP register.
reg := AllocReg(freeFPRegs);
if reg <> 0 then
begin
Arg.ExtractRawData(@block.RegS[RegFlagToIndex(reg)]);
Exit;
end;
end
else if Arg.DataSize = 8 then // Double and Extended
begin
// First, allocate one Double VFP register.
reg := AllocDoubleReg;
if reg <> 0 then
begin
Arg.ExtractRawData(@block.RegD[RegDoubleFlagToIndex(reg)]);
Exit;
end;
end;
end
else if (Arg.Kind = tkRecord) then
begin
src := Arg.GetReferenceToRawData;
while datasize > 0 do
begin
reg := AllocReg(freeCRegs);
if reg <> regNone then
begin
Move(src^, block.RegCR[RegFlagToIndex(reg)], 4);
end
else
begin
Move(src^, top^, 4);
//Inc(top, Align4(dataSize));
Inc(top, 4);
end;
Dec(dataSize, 4);
Inc(Src, 4);
end;
Exit;
end;
if (dataSize in [1, 2, 4]) then
begin
reg := AllocReg(freeCRegs);
if reg <> regNone then
begin
Arg.ExtractRawDataNoCopy(@block.RegCR[RegFlagToIndex(reg)]);
Exit;
end;
Arg.ExtractRawDataNoCopy(top);
Inc(top, Align4(dataSize));
end
else if (dataSize in [8]) then // 64bit data
begin
// Next, allocate two core register
{$IFDEF IOS}
regL := AllocReg(freeCRegs);
{$ELSE}
regL := AllocEvenReg(freeCRegs);
{$ENDIF}
regH := AllocReg(freeCRegs);
if (Arg.Kind = tkFloat) then
begin
case Arg.TypeData.FloatType of
ftSingle,
ftDouble,
ftExtended:
PDouble(@U64)^ := Arg.AsExtended;
ftComp,
ftCurr:
Arg.ExtractRawDataNoCopy(@U64);
end;
end
else
U64 := Arg.AsUInt64;
L32 := U64 and $FFFFFFFF;
H32 := (U64 shr 32) and $FFFFFFFF;
if regL <> 0 then
begin
block.RegCR[ RegFlagToIndex(regL)] := L32;
if regH <> 0 then
block.RegCR[ RegFlagToIndex(regH)] := H32
else // regH = 0;
begin
PCardinal(top)^ := H32;
Inc(top, SizeOf(H32)); // 4
end;
end
else // if regL = 0, regH also 0.
begin
{$IF not defined(IOS)}
if ((NativeInt(top) - NativeInt(@stackData[0])) mod 8) <> 0 then
Inc(top, 4); // Set 8 byte align
{$ENDIF !IOS}
PCardinal(top)^ := L32;
Inc(top, SizeOf(L32)); // 4
PCardinal(top)^ := H32;
Inc(top, SizeOf(H32)); // 4
end;
end
else
Assert(False, 'somethig wrong');
end;
procedure PutRefArg(const Loc: Pointer);
var
reg: UInt32;
begin
reg := AllocReg(freeCRegs);
if reg <> regNone then
begin
block.RegCR[ RegFlagToIndex(reg)] := UInt32(Loc);
Exit;
end;
PPointer(top)^ := Loc;
Inc(top, SizeOf(Pointer));
end;
var
i : integer;
begin
FillChar(block, SizeOf(block), 0);
SetLength(stackData, CalcStackSize);
top := @stackData[0];
freeCRegs := regCRAll;
freeFPRegs := regNone;
{$IFDEF ANDROID}
if CallingConvention in [ccReg] then
freeFPRegs := regFPRAll;
{$ENDIF ANDROID}
if IsStatic then
begin
if (CallingConvention <> ccSafeCall) and UseResultPointer(AResultType, IsConstructor) then
begin
TValue.Make(nil, AResultType, Result);
PutRefArg(Result.GetReferenceToRawData);
end;
if Length(Args) > 0 then
if PassByRef(Args[0].TypeInfo, CallingConvention) or ByRefs[0] then
PutRefArg(Args[0].GetReferenceToRawData)
else
PutArg(Args[0]);
end
else
begin // not IsStatic / It class method
// Put result first.
if (CallingConvention <> ccSafeCall) and UseResultPointer(AResultType, IsConstructor) then
begin
TValue.Make(nil, AResultType, Result);
PutRefArg(Result.GetReferenceToRawData);
end;
// First arg is "self". place to 2nd.
if Length(Args) > 0 then
if PassByRef(Args[0].TypeInfo, CallingConvention) or ByRefs[0] then
PutRefArg(Args[0].GetReferenceToRawData)
else
PutArg(Args[0]);
end;
for i := 1 to Length(Args) - 1 do
if PassByRef(Args[i].TypeInfo, CallingConvention) or ByRefs[i] then
PutRefArg(Args[i].GetReferenceToRawData)
else
PutArg(Args[i]);
if CallingConvention = ccSafeCall then
begin
TValue.Make(nil, AResultType, Result);
PutRefArg(Result.GetReferenceToRawData);
end;
block.StackData := @stackData[0];
block.StackDataSize := top - PByte(@stackData[0]);
RawInvoke(CodeAddress, @block);
if AResultType = nil then
Result := TValue.Empty
else if UseResultPointer(AResultType, IsConstructor) then
// do nothing
{$IFDEF ANDROID}
else if (CallingConvention = ccReg) and
(AResultType^.Kind = tkFloat) and
(AResultType.TypeData.FloatType in [ftSingle, ftDouble, ftExtended]) then
TValue.MakeWithoutCopy(@block.RegD[0], AResultType, Result)
{$ENDIF ANDROID}
else
TValue.Make(@block.RegCR[0], AResultType, Result);
end;
end.