###########################################################################
# A module to create a model of calling conventions
#
# Copyright (C) 2009-2011 Institute for System Programming, RAS
# Copyright (C) 2011-2012 Nokia Corporation and/or its subsidiary(-ies)
# Copyright (C) 2012-2018 Andrey Ponomarenko's ABI Laboratory
#
# Written by Andrey Ponomarenko
#
# PLATFORMS
# =========
#  Linux, FreeBSD, Solaris and Mac OS X
#    x86 - System V ABI Intel386 Architecture Processor Supplement
#    x86_64 - System V ABI AMD64 Architecture Processor Supplement
#
#  MS Windows
#    x86 - MSDN Argument Passing and Naming Conventions
#    x86_64 - MSDN x64 Software Conventions
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA  02110-1301 USA
###########################################################################
use strict;

my $BYTE = 8;

my %UsedReg = ();
my %UsedStack = ();

my %IntAlgn = (
    "x86"=>{
        "double"=>4,
        "long double"=>4
    }
);

sub classifyType($$)
{
    my ($Tid, $LVer) = @_;
    
    my %Type = getPureType($Tid, $LVer);
    my $Arch = $In::ABI{$LVer}{"Arch"};
    
    my %Classes = ();
    if($Type{"Name"} eq "void")
    {
        $Classes{0}{"Class"} = "VOID";
        return %Classes;
    }
    if($In::Opt{"Target"}=~/\A(unix|linux|macos|freebsd|solaris)\Z/)
    { # GCC
        if($Arch eq "x86")
        {
            if(isFloat($Type{"Name"})) {
                $Classes{0}{"Class"} = "FLOAT";
            }
            elsif($Type{"Type"}=~/Intrinsic|Enum|Pointer|Ptr/) {
                $Classes{0}{"Class"} = "INTEGRAL";
            }
            else { # Struct, Class, Union
                $Classes{0}{"Class"} = "MEMORY";
            }
        }
        elsif($Arch eq "x86_64")
        {
            if($Type{"Type"}=~/Enum|Pointer|Ptr/
            or isScalar($Type{"Name"})
            or $Type{"Name"}=~/\A(_Bool|bool)\Z/) {
                $Classes{0}{"Class"} = "INTEGER";
            }
            elsif($Type{"Name"} eq "__int128"
            or $Type{"Name"} eq "unsigned __int128")
            {
                $Classes{0}{"Class"} = "INTEGER";
                $Classes{1}{"Class"} = "INTEGER";
            }
            elsif($Type{"Name"}=~/\A(float|double|_Decimal32|_Decimal64|__m64)\Z/) {
                $Classes{0}{"Class"} = "SSE";
            }
            elsif($Type{"Name"}=~/\A(__float128|_Decimal128|__m128)\Z/)
            {
                $Classes{0}{"Class"} = "SSE";
                $Classes{8}{"Class"} = "SSEUP";
            }
            elsif($Type{"Name"} eq "__m256")
            {
                $Classes{0}{"Class"} = "SSE";
                $Classes{24}{"Class"} = "SSEUP";
            }
            elsif($Type{"Name"} eq "long double")
            {
                $Classes{0}{"Class"} = "X87";
                $Classes{8}{"Class"} = "X87UP";
            }
            elsif($Type{"Name"}=~/\Acomplex (float|double)\Z/) {
                $Classes{0}{"Class"} = "MEMORY";
            }
            elsif($Type{"Name"} eq "complex long double") {
                $Classes{0}{"Class"} = "COMPLEX_X87";
            }
            elsif($Type{"Type"}=~/Struct|Class|Union|Array/)
            {
                if($Type{"Size"}>4*8) {
                    $Classes{0}{"Class"} = "MEMORY";
                }
                else {
                    %Classes = classifyAggregate($Tid, $LVer);
                }
            }
            else {
                $Classes{0}{"Class"} = "MEMORY";
            }
        }
        elsif($Arch eq "arm")
        {
        }
    }
    elsif($In::Opt{"Target"} eq "windows")
    { # MS C++ Compiler
        if($Arch eq "x86")
        {
            if(isFloat($Type{"Name"})) {
                $Classes{0}{"Class"} = "FLOAT";
            }
            elsif($Type{"Type"}=~/Intrinsic|Enum|Pointer|Ptr/) {
                $Classes{0}{"Class"} = "INTEGRAL";
            }
            elsif($Type{"Type"}=~/\A(Struct|Union)\Z/ and $Type{"Size"}<=8) {
                $Classes{0}{"Class"} = "POD";
            }
            else { # Struct, Class, Union
                $Classes{0}{"Class"} = "MEMORY";
            }
        }
        elsif($Arch eq "x86_64")
        {
            if($Type{"Name"}=~/\A(float|double|long double)\Z/) {
                $Classes{0}{"Class"} = "FLOAT";
            }
            elsif($Type{"Name"}=~/\A__m128(|i|d)\Z/) {
                $Classes{0}{"Class"} = "M128";
            }
            elsif(isScalar($Type{"Name"})
            or $Type{"Type"}=~/Enum|Pointer|Ptr/
            or $Type{"Name"}=~/\A(_Bool|bool)\Z/
            or ($Type{"Type"}=~/\A(Struct|Union)\Z/ and $Type{"Size"}<=8)
            or $Type{"Name"} eq "__m64") {
                $Classes{0}{"Class"} = "INTEGRAL";
            }
            else {
                $Classes{0}{"Class"} = "MEMORY";
            }
        }
    }
    return %Classes;
}

sub classifyAggregate($$)
{
    my ($Tid, $LVer) = @_;
    
    my %Type = getPureType($Tid, $LVer);
    my $Word = $In::ABI{$LVer}{"WordSize"};
    my $Arch = $In::ABI{$LVer}{"Arch"};
    
    my %Group = ();
    my $GroupID = 0;
    my %Classes = ();
    my %Offsets = ();
    if($Type{"Type"} eq "Array")
    {
        my %Base = getOneStepBaseType($Tid, $LVer);
        my %BaseType = getPureType($Base{"Tid"}, $LVer);
        my $Pos = 0;
        my $Max = 0;
        if(my $BSize = $BaseType{"Size"}) {
            $Max = ($Type{"Size"}/$BSize) - 1;
        }
        foreach my $Pos (0 .. $Max)
        {
            # if($TInfo->{1}{"Name"} eq "void")
            # { # DWARF ABI Dump
            #     $Type{"Memb"}{$Pos}{"offset"} = $Type{"Size"}/($Max+1);
            # }
            $Type{"Memb"}{$Pos}{"algn"} = getAlignment_Model($BaseType{"Tid"}, $LVer);
            $Type{"Memb"}{$Pos}{"type"} = $BaseType{"Tid"};
            $Type{"Memb"}{$Pos}{"name"} = "[$Pos]";
        }
    }
    if($Type{"Type"} eq "Union")
    {
        foreach my $Pos (keys(%{$Type{"Memb"}}))
        {
            $Offsets{$Pos} = $Pos;
            $Group{0}{$Pos} = 1;
        }
    }
    else
    { # Struct, Class
        foreach my $Pos (keys(%{$Type{"Memb"}}))
        {
            my $Offset = getOffset($Pos, \%Type, $LVer)/$BYTE;
            $Offsets{$Pos} = $Offset;
            my $GroupOffset = int($Offset/$Word)*$Word;
            $Group{$GroupOffset}{$Pos} = 1;
        }
    }
    foreach my $GroupOffset (sort {$a<=>$b} (keys(%Group)))
    {
        my %GroupClasses = ();
        foreach my $Pos (sort {$a<=>$b} (keys(%{$Group{$GroupOffset}})))
        { # split the field into the classes
            my $MTid = $Type{"Memb"}{$Pos}{"type"};
            my $MName = $Type{"Memb"}{$Pos}{"name"};
            my %SubClasses = classifyType($MTid, $LVer);
            foreach my $Offset (sort {$a<=>$b} keys(%SubClasses))
            {
                if(defined $SubClasses{$Offset}{"Elems"})
                {
                    foreach (keys(%{$SubClasses{$Offset}{"Elems"}})) {
                        $SubClasses{$Offset}{"Elems"}{$_} = joinFields($MName, $SubClasses{$Offset}{"Elems"}{$_});
                    }
                }
                else {
                    $SubClasses{$Offset}{"Elems"}{0} = $MName;
                }
            }
            
            # add to the group
            foreach my $Offset (sort {$a<=>$b} keys(%SubClasses)) { 
                $GroupClasses{$Offsets{$Pos}+$Offset} = $SubClasses{$Offset};
            }
        }
        
        # merge classes in the group
        my %MergeGroup = ();
        
        foreach my $Offset (sort {$a<=>$b} keys(%GroupClasses)) {
            $MergeGroup{int($Offset/$Word)}{$Offset} = $GroupClasses{$Offset};
        }
        
        foreach my $Offset (sort {$a<=>$b} keys(%MergeGroup)) {
            while(postMerger($Arch, $MergeGroup{$Offset})) { };
        }
        
        %GroupClasses = ();
        foreach my $M_Offset (sort {$a<=>$b} keys(%MergeGroup))
        {
            foreach my $Offset (sort {$a<=>$b} keys(%{$MergeGroup{$M_Offset}}))
            {
                $GroupClasses{$Offset} = $MergeGroup{$M_Offset}{$Offset};
            }
        }
        
        # add to the result list of classes
        foreach my $Offset (sort {$a<=>$b} keys(%GroupClasses))
        {
            if($Type{"Type"} eq "Union")
            {
                foreach my $P (keys(%{$GroupClasses{$Offset}{"Elems"}}))
                {
                    if($P!=0) {
                        delete($GroupClasses{$Offset}{"Elems"}{$P});
                    }
                }
            }
            $Classes{$Offset} = $GroupClasses{$Offset};
        }
    }
    
    return %Classes;
}

sub postMerger($$)
{
    my ($Arch, $PreClasses) = @_;
    my @Offsets = sort {$a<=>$b} keys(%{$PreClasses});
    if($#Offsets==0) {
        return 0;
    }
    my %PostClasses = ();
    my $Num = 0;
    my $Merged = 0;
    while($Num<=$#Offsets-1)
    {
        my $Offset1 = $Offsets[$Num];
        my $Offset2 = $Offsets[$Num+1];
        my $Class1 = $PreClasses->{$Offset1}{"Class"};
        my $Class2 = $PreClasses->{$Offset2}{"Class"};
        my $ResClass = "";
        
        if($In::Opt{"Target"}=~/\A(unix|linux|macos|freebsd|solaris)\Z/)
        { # GCC
            if($Arch eq "x86_64")
            {
                if($Class1 eq $Class2) {
                    $ResClass = $Class1;
                }
                elsif($Class1 eq "MEMORY"
                or $Class2 eq "MEMORY") {
                    $ResClass = "MEMORY";
                }
                elsif($Class1 eq "INTEGER"
                or $Class2 eq "INTEGER") {
                    $ResClass = "INTEGER";
                }
                elsif($Class1=~/X87/
                or $Class2=~/X87/) {
                    $ResClass = "MEMORY";
                }
                else {
                    $ResClass = "SSE";
                }
            }
        }
        
        if($ResClass)
        { # combine
            $PostClasses{$Offset1}{"Class"} = $ResClass;
            foreach (keys(%{$PreClasses->{$Offset1}{"Elems"}})) {
                $PostClasses{$Offset1}{"Elems"}{$Offset1+$_} = $PreClasses->{$Offset1}{"Elems"}{$_};
            }
            foreach (keys(%{$PreClasses->{$Offset2}{"Elems"}})) {
                $PostClasses{$Offset1}{"Elems"}{$Offset2+$_} = $PreClasses->{$Offset2}{"Elems"}{$_};
            }
            $Merged = 1;
        }
        else
        { # save unchanged
            $PostClasses{$Offset1} = $PreClasses->{$Offset1};
            $PostClasses{$Offset2} = $PreClasses->{$Offset2};
        }
        $Num += 2;
    }
    if($Num==$#Offsets) {
        $PostClasses{$Offsets[$Num]} = $PreClasses->{$Offsets[$Num]};
    }
    %{$PreClasses} = %PostClasses;
    return $Merged;
}

sub joinFields($$)
{
    my ($F1, $F2) = @_;
    if(substr($F2, 0, 1) eq "[")
    { # array elements
        return $F1.$F2;
    }
    else { # fields
        return $F1.".".$F2;
    }
}

sub callingConvention_R_Model($$$) {
    return callingConvention_R_I_Model(@_, 1);
}

sub callingConvention_R_I_Model($$$)
{
    my ($SInfo, $LVer, $Target) = @_;
    my %Conv = ();
    my $RTid = $SInfo->{"Return"};
    my %Type = getPureType($RTid, $LVer);
    my $Word = $In::ABI{$LVer}{"WordSize"};
    my $Arch = $In::ABI{$LVer}{"Arch"};
    
    if($Target) {
        %UsedReg = ();
    }
    
    my %UsedReg_Copy = %UsedReg;
    
    my %Classes = classifyType($RTid, $LVer);
    
    foreach my $Offset (sort {$a<=>$b} keys(%Classes))
    {
        my $Elems = undef;
        if(defined $Classes{$Offset}{"Elems"})
        {
            foreach (keys(%{$Classes{$Offset}{"Elems"}})) {
                $Classes{$Offset}{"Elems"}{$_} = joinFields(".result", $Classes{$Offset}{"Elems"}{$_});
            }
            $Elems = $Classes{$Offset}{"Elems"};
        }
        else {
            $Elems = { 0 => ".result" };
        }
        
        my $CName = $Classes{$Offset}{"Class"};
        
        if($CName eq "VOID") {
            next;
        }
        
        if($In::Opt{"Target"}=~/\A(unix|linux|macos|freebsd|solaris)\Z/)
        { # GCC
            if($Arch eq "x86")
            {
                if($CName eq "FLOAT")
                { # x87 register
                    useRegister("st0", "f", $Elems, $SInfo);
                }
                elsif($CName eq "INTEGRAL")
                {
                    useRegister("eax", "f", $Elems, $SInfo);
                }
                elsif($CName eq "MEMORY") {
                    pushStack_R($SInfo, $Word);
                }
            }
            elsif($Arch eq "x86_64")
            {
                my @INT = ("rax", "rdx");
                my @SSE = ("xmm0", "xmm1");
                if($CName eq "INTEGER")
                {
                    if(my $R = getLastAvailable($SInfo, "f", @INT))
                    {
                        useRegister($R, "f", $Elems, $SInfo);
                    }
                    else
                    { # revert registers
                      # pass as MEMORY
                        %UsedReg = %UsedReg_Copy;
                        useHidden($SInfo, $Arch, $Word);
                        $Conv{"Hidden"} = 1;
                        last;
                    }
                }
                elsif($CName eq "SSE")
                {
                    if(my $R = getLastAvailable($SInfo, "8l", @SSE))
                    {
                        useRegister($R, "8l", $Elems, $SInfo);
                    }
                    else
                    {
                        %UsedReg = %UsedReg_Copy;
                        useHidden($SInfo, $Arch, $Word);
                        $Conv{"Hidden"} = 1;
                        last;
                    }
                }
                elsif($CName eq "SSEUP")
                {
                    if(my $R = getLastUsed($SInfo, "xmm0", "xmm1"))
                    {
                        useRegister($R, "8h", $Elems, $SInfo);
                    }
                    else
                    {
                        %UsedReg = %UsedReg_Copy;
                        useHidden($SInfo, $Arch, $Word);
                        $Conv{"Hidden"} = 1;
                        last;
                    }
                }
                elsif($CName eq "X87")
                {
                    useRegister("st0", "8l", $Elems, $SInfo);
                }
                elsif($CName eq "X87UP")
                {
                    useRegister("st0", "8h", $Elems, $SInfo);
                }
                elsif($CName eq "COMPLEX_X87")
                {
                    useRegister("st0", "f", $Elems, $SInfo);
                    useRegister("st1", "f", $Elems, $SInfo);
                }
                elsif($CName eq "MEMORY")
                {
                    useHidden($SInfo, $Arch, $Word);
                    $Conv{"Hidden"} = 1;
                    last;
                }
            }
            elsif($Arch eq "arm")
            { # TODO
            }
        }
        elsif($In::Opt{"Target"} eq "windows")
        { # MS C++ Compiler
            if($Arch eq "x86")
            {
                if($CName eq "FLOAT")
                {
                    useRegister("fp0", "f", $Elems, $SInfo);
                }
                elsif($CName eq "INTEGRAL")
                {
                    useRegister("eax", "f", $Elems, $SInfo);
                }
                elsif($CName eq "POD")
                {
                    useRegister("eax", "f", $Elems, $SInfo);
                    useRegister("edx", "f", $Elems, $SInfo);
                }
                elsif($CName eq "MEMORY" or $CName eq "M128")
                {
                    useHidden($SInfo, $Arch, $Word);
                    $Conv{"Hidden"} = 1;
                }
            }
            elsif($Arch eq "x86_64")
            {
                if($CName eq "FLOAT" or $CName eq "M128")
                {
                    useRegister("xmm0", "f", $Elems, $SInfo);
                }
                elsif($CName eq "INTEGRAL")
                {
                    useRegister("eax", "f", $Elems, $SInfo);
                }
                elsif($CName eq "MEMORY")
                {
                    useHidden($SInfo, $Arch, $Word);
                    $Conv{"Hidden"} = 1;
                }
            }
        }
    }
    
    
    if(my %Regs = usedBy(".result", $SInfo))
    {
        $Conv{"Method"} = "reg";
        $Conv{"Registers"} = join(", ", sort(keys(%Regs)));
    }
    elsif(my %Regs = usedBy(".result_ptr", $SInfo))
    {
        $Conv{"Method"} = "reg";
        $Conv{"Registers"} = join(", ", sort(keys(%Regs)));
    }
    
    if(not $Conv{"Method"})
    { # unknown
        if($Type{"Name"} ne "void")
        {
            $Conv{"Method"} = "stack";
            $Conv{"Hidden"} = 1;
        }
    }
    
    return %Conv;
}

sub usedBy($$)
{
    my ($Name, $SInfo) = @_;
    my %Regs = ();
    foreach my $Reg (sort keys(%{$UsedReg{$SInfo}}))
    {
        foreach my $Size (sort keys(%{$UsedReg{$SInfo}{$Reg}}))
        {
            foreach my $Offset (sort keys(%{$UsedReg{$SInfo}{$Reg}{$Size}}))
            {
                if($UsedReg{$SInfo}{$Reg}{$Size}{$Offset}=~/\A\Q$Name\E(\.|\Z)/) {
                    $Regs{$Reg} = 1;
                }
            }
        }
    }
    return %Regs;
}

sub useHidden($$$)
{
    my ($SInfo, $Arch, $Word) = @_;
    if($In::Opt{"Target"}=~/\A(unix|linux|macos|freebsd|solaris)\Z/)
    { # GCC
        if($Arch eq "x86") {
            pushStack_R($SInfo, $Word);
        }
        elsif($Arch eq "x86_64")
        {
            my $Elems = { 0 => ".result_ptr" };
            useRegister("rdi", "f", $Elems, $SInfo);
        }
    }
    elsif($In::Opt{"Target"} eq "windows")
    { # MS C++ Compiler
        if($Arch eq "x86") {
            pushStack_R($SInfo, $Word);
        }
        elsif($Arch eq "x86_64")
        {
            my $Elems = { 0 => ".result_ptr" };
            useRegister("rcx", "f", $Elems, $SInfo);
        }
    }
}

sub pushStack_P($$$$)
{
    my ($SInfo, $Pos, $TInfo, $StackAlgn) = @_;
    my $PTid = $SInfo->{"Param"}{$Pos}{"type"};
    my $PName = $SInfo->{"Param"}{$Pos}{"name"};
    
    if(my $Offset = $SInfo->{"Param"}{$Pos}{"offset"})
    { # DWARF ABI Dump
        return pushStack_Offset($SInfo, $Offset, $TInfo->{$PTid}{"Size"}, { 0 => $PName });
    }
    else
    {
        my $Alignment = $SInfo->{"Param"}{$Pos}{"algn"};
        if($Alignment<$StackAlgn) {
            $Alignment = $StackAlgn;
        }
        return pushStack($SInfo, $Alignment, $TInfo->{$PTid}{"Size"}, { 0 => $PName });
    }
}

sub pushStack_R($$)
{
    my ($SInfo, $Word) = @_;
    return pushStack($SInfo, $Word, $Word, { 0 => ".result_ptr" });
}

sub pushStack_C($$$)
{
    my ($SInfo, $Class, $TInfo) = @_;
    return pushStack($SInfo, $Class->{"Algn"}, $Class->{"Size"}, $Class->{"Elems"});
}

sub pushStack($$$$)
{
    my ($SInfo, $Algn, $Size, $Elem) = @_;
    my $Offset = 0;
    if(my @Offsets = sort {$a<=>$b} keys(%{$UsedStack{$SInfo}}))
    {
        $Offset = $Offsets[$#Offsets];
        $Offset += $UsedStack{$SInfo}{$Offset}{"Size"};
        $Offset += getPadding($Offset, $Algn);
    }
    return pushStack_Offset($SInfo, $Offset, $Size, $Elem);
}

sub pushStack_Offset($$$$)
{
    my ($SInfo, $Offset, $Size, $Elem) = @_;
    my %Info = (
        "Size" => $Size,
        "Elem" => $Elem
    );
    $UsedStack{$SInfo}{$Offset} = \%Info;
    return $Offset;
}

sub useRegister($$$$)
{
    my ($R, $Offset, $Elems, $SInfo) = @_;
    if(defined $UsedReg{$SInfo}{$R})
    {
        if(defined $UsedReg{$SInfo}{$R}{$Offset})
        { # busy
            return 0;
        }
    }
    $UsedReg{$SInfo}{$R}{$Offset}=$Elems;
    return $R;
}

sub getLastAvailable(@)
{
    my $SInfo = shift(@_);
    my $Offset = shift(@_);
    my $Pos = 0;
    foreach (@_)
    {
        if(not defined $UsedReg{$SInfo}{$_}) {
            return $_;
        }
        elsif(not defined $UsedReg{$SInfo}{$_}{$Offset}) {
            return $_;
        }
    }
    return undef;
}

sub getLastUsed(@)
{
    my $SInfo = shift(@_);
    my $Pos = 0;
    foreach (@_)
    {
        if(not defined $UsedReg{$SInfo}{$_})
        {
            if($Pos>0) {
                return @_[$Pos-1];
            }
            else {
                return @_[0];
            }
        }
        $Pos+=1;
    }
    return undef;
}

sub callingConvention_P_Model($$$) {
    return callingConvention_P_I_Model(@_, 1);
}

sub callingConvention_P_I_Model($$$$)
{ # calling conventions for different compilers and operating systems
    my ($SInfo, $Pos, $LVer, $Target) = @_;
    
    my $TInfo = $In::ABI{$LVer}{"TypeInfo"};
    my $PTid = $SInfo->{"Param"}{$Pos}{"type"};
    my $PName = $SInfo->{"Param"}{$Pos}{"name"};
    my %Type = getPureType($PTid, $LVer);
    my $Word = $In::ABI{$LVer}{"WordSize"};
    my $Arch = $In::ABI{$LVer}{"Arch"};
    
    if($Target)
    {
        %UsedReg = ();
        
        # distribute return value
        if(my $RTid = $SInfo->{"Return"}) {
            callingConvention_R_I_Model($SInfo, $LVer, 0);
        }
        # distribute other parameters
        if($Pos>0)
        {
            my %PConv = ();
            my $PPos = 0;
            while($PConv{"Next"} ne $Pos)
            {
                %PConv = callingConvention_P_I_Model($SInfo, $PPos++, $LVer, 0);
                if(not $PConv{"Next"}) {
                    last;
                }
            }
        }
    }
    
    my %UsedReg_Copy = %UsedReg;
    
    my %Classes = classifyType($PTid, $LVer);
    
    my $Error = 0;
    foreach my $Offset (sort {$a<=>$b} keys(%Classes))
    {
        my $Elems = undef;
        if(defined $Classes{$Offset}{"Elems"})
        {
            foreach (keys(%{$Classes{$Offset}{"Elems"}})) {
                $Classes{$Offset}{"Elems"}{$_} = joinFields($PName, $Classes{$Offset}{"Elems"}{$_});
            }
            $Elems = $Classes{$Offset}{"Elems"};
        }
        else {
            $Elems = { 0 => $PName };
        }
        
        my $CName = $Classes{$Offset}{"Class"};
        
        if($CName eq "VOID") {
            next;
        }
        
        if($In::Opt{"Target"}=~/\A(unix|linux|macos|freebsd|solaris)\Z/)
        { # GCC
            if($Arch eq "x86")
            {
                pushStack_P($SInfo, $Pos, $TInfo, $Word);
                last;
            }
            elsif($Arch eq "x86_64")
            {
                my @INT = ("rdi", "rsi", "rdx", "rcx", "r8", "r9");
                my @SSE = ("xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7");
                
                if($CName eq "INTEGER")
                {
                    if(my $R = getLastAvailable($SInfo, "f", @INT)) {
                        useRegister($R, "f", $Elems, $SInfo);
                    }
                    else
                    { # revert registers and
                      # push the argument on the stack
                        %UsedReg = %UsedReg_Copy;
                        pushStack_P($SInfo, $Pos, $TInfo, $Word);
                        last;
                    }
                }
                elsif($CName eq "SSE")
                {
                    if(my $R = getLastAvailable($SInfo, "8l", @SSE)) {
                        useRegister($R, "8l", $Elems, $SInfo);
                    }
                    else
                    {
                        %UsedReg = %UsedReg_Copy;
                        pushStack_P($SInfo, $Pos, $TInfo, $Word);
                        last;
                    }
                }
                elsif($CName eq "SSEUP")
                {
                    if(my $R = getLastUsed($SInfo, @SSE)) {
                        useRegister($R, "8h", $Elems, $SInfo);
                    }
                    else
                    {
                        %UsedReg = %UsedReg_Copy;
                        pushStack_P($SInfo, $Pos, $TInfo, $Word);
                        last;
                    }
                }
                elsif($CName=~/X87|MEMORY/)
                { # MEMORY, X87, X87UP, COMPLEX_X87
                    pushStack_P($SInfo, $Pos, $TInfo, $Word);
                    last;
                }
                else
                {
                    pushStack_P($SInfo, $Pos, $TInfo, $Word);
                    last;
                }
            }
            elsif($Arch eq "arm")
            { # Procedure Call Standard for the ARM Architecture
              # TODO
                pushStack_P($SInfo, $Pos, $TInfo, $Word);
                last;
            }
            else
            { # TODO
                pushStack_P($SInfo, $Pos, $TInfo, $Word);
                last;
            }
        }
        elsif($In::Opt{"Target"} eq "windows")
        { # MS C++ Compiler
            if($Arch eq "x86")
            {
                pushStack_P($SInfo, $Pos, $TInfo, $Word);
                last;
            }
            elsif($Arch eq "x86_64")
            {
                if($Pos<=3)
                {
                    if($CName eq "FLOAT")
                    {
                        useRegister("xmm".$Pos, "8l", $Elems, $SInfo);
                    }
                    elsif($CName eq "INTEGRAL")
                    {
                        if($Pos==0) {
                            useRegister("rcx", "f", $Elems, $SInfo);
                        }
                        elsif($Pos==1) {
                            useRegister("rdx", "f", $Elems, $SInfo);
                        }
                        elsif($Pos==2) {
                            useRegister("r8", "f", $Elems, $SInfo);
                        }
                        elsif($Pos==3) {
                            useRegister("r9", "f", $Elems, $SInfo);
                        }
                        else
                        {
                            pushStack_P($SInfo, $Pos, $TInfo, $Word);
                            last;
                        }
                    }
                    else
                    {
                        pushStack_P($SInfo, $Pos, $TInfo, $Word);
                        last;
                    }
                }
                else
                {
                    pushStack_P($SInfo, $Pos, $TInfo, $Word);
                    last;
                }
            }
        }
        else
        { # TODO
            pushStack_P($SInfo, $Pos, $TInfo, $Word);
            last;
        }
    }
    
    my %Conv = ();
    
    if(my %Regs = usedBy($PName, $SInfo))
    {
        $Conv{"Method"} = "reg";
        $Conv{"Registers"} = join(", ", sort(keys(%Regs)));
    }
    else
    {
        if($Type{"Name"} ne "void") {
            $Conv{"Method"} = "stack";
        }
    }
    
    if(defined $SInfo->{"Param"}{$Pos+1})
    { # TODO
        $Conv{"Next"} = $Pos+1;
    }
    
    return %Conv;
}

sub getAlignment_Model($$)
{
    my ($Tid, $LVer) = @_;
    
    if(not $Tid)
    { # incomplete ABI dump
        return 0;
    }
    
    my $TInfo = $In::ABI{$LVer}{"TypeInfo"};
    
    if(defined $TInfo->{$Tid}{"Algn"}) {
        return $TInfo->{$Tid}{"Algn"};
    }
    else
    {
        if($TInfo->{$Tid}{"Type"}=~/Struct|Class|Union|MethodPtr/)
        {
            if(defined $TInfo->{$Tid}{"Memb"})
            {
                my $Max = 0;
                foreach my $Pos (keys(%{$TInfo->{$Tid}{"Memb"}}))
                {
                    my $Algn = $TInfo->{$Tid}{"Memb"}{$Pos}{"algn"};
                    if(not $Algn) {
                        $Algn = getAlignment_Model($TInfo->{$Tid}{"Memb"}{$Pos}{"type"}, $LVer);
                    }
                    if($Algn>$Max) {
                        $Max = $Algn;
                    }
                }
                return $Max;
            }
            return 0;
        }
        elsif($TInfo->{$Tid}{"Type"} eq "Array")
        {
            my %Base = getOneStepBaseType($Tid, $LVer);
            
            if($Base{"Tid"} eq $Tid)
            { # emergency exit
                return 0;
            }
            
            return getAlignment_Model($Base{"Tid"}, $LVer);
        }
        elsif($TInfo->{$Tid}{"Type"}=~/Intrinsic|Enum|Pointer|FuncPtr/)
        { # model
            return getIntAlgn($Tid, $LVer);
        }
        else
        {
            my %PureType = getPureType($Tid, $LVer);
            
            if($PureType{"Tid"} eq $Tid)
            { # emergency exit
                return 0;
            }
            
            return getAlignment_Model($PureType{"Tid"}, $LVer);
        }
    }
}

sub getIntAlgn($$)
{
    my ($Tid, $LVer) = @_;
    
    my $Name = $In::ABI{$LVer}{"TypeInfo"}{$Tid}{"Name"};
    my $Arch = $In::ABI{$LVer}{"Arch"};
    
    if(my $Algn = $IntAlgn{$Arch}{$Name}) {
        return $Algn;
    }
    else
    {
        my $Size = $In::ABI{$LVer}{"TypeInfo"}{$Tid}{"Size"};
        if($Arch eq "x86_64")
        { # x86_64: sizeof==alignment
            return $Size;
        }
        elsif($Arch eq "arm")
        {
            if($Size>8)
            { # 128-bit vector (16)
                return 8;
            }
            return $Size;
        }
        elsif($Arch eq "x86")
        {
            if($Size>4)
            { # "double" (8) and "long double" (12)
                return 4;
            }
            return $Size;
        }
        return $Size;
    }
}

sub getAlignment($$$)
{
    my ($Pos, $TypePtr, $LVer) = @_;
    my $Tid = $TypePtr->{"Memb"}{$Pos}{"type"};
    
    my $TSize = $In::ABI{$LVer}{"TypeInfo"}{$Tid}{"Size"};
    
    my $Computed = $TypePtr->{"Memb"}{$Pos}{"algn"};
    my  $Alignment = 0;
    
    if(my $BSize = $TypePtr->{"Memb"}{$Pos}{"bitfield"})
    { # bitfields
        if($Computed)
        { # real in bits
            $Alignment = $Computed;
        }
        else
        { # model
            if($BSize eq $TSize*$BYTE) {
                $Alignment = $BSize;
            }
            else {
                $Alignment = 1;
            }
        }
        return ($Alignment, $BSize);
    }
    else
    { # other fields
        if($Computed)
        { # real in bytes
            $Alignment = $Computed*$BYTE;
        }
        else
        { # model
            $Alignment = getAlignment_Model($Tid, $LVer)*$BYTE;
        }
        return ($Alignment, $TSize*$BYTE);
    }
}

sub getOffset($$$)
{ # offset of the field including padding
    my ($FieldPos, $TypePtr, $LVer) = @_;
    
    if($TypePtr->{"Type"} eq "Union") {
        return 0;
    }
    
    # if((my $Off = $TypePtr->{"Memb"}{$FieldPos}{"offset"}) ne "")
    # { # DWARF ABI Dump (generated by the ABI Dumper tool)
    #    return $Off*$BYTE;
    # }
    
    my $Offset = 0;
    my $Buffer = 0;
    my $Word = $In::ABI{$LVer}{"WordSize"};
    
    foreach my $Pos (0 .. keys(%{$TypePtr->{"Memb"}})-1)
    {
        my ($Alignment, $MSize) = getAlignment($Pos, $TypePtr, $LVer);
        
        if(not $Alignment)
        { # support for old ABI dumps
            if($MSize=~/\A(8|16|32|64)\Z/)
            {
                if($Buffer+$MSize<$Word*$BYTE)
                {
                    $Alignment = 1;
                    $Buffer += $MSize;
                }
                else
                {
                    $Alignment = $MSize;
                    $Buffer = 0;
                }
            }
            else
            {
                $Alignment = 1;
                $Buffer += $MSize;
            }
        }
        
        # padding
        $Offset += getPadding($Offset, $Alignment);
        if($Pos==$FieldPos)
        { # after the padding
          # before the field
            return $Offset;
        }
        $Offset += $MSize;
    }
    return $FieldPos; # if something is going wrong
}

sub getPadding($$)
{
    my ($Offset, $Alignment) = @_;
    my $Padding = 0;
    if($Offset % $Alignment!=0)
    { # not aligned, add padding
        $Padding = $Alignment - $Offset % $Alignment;
    }
    return $Padding;
}

sub isMemPadded($$$$$)
{ # check if the target field can be added/removed/changed
  # without shifting other fields because of padding bits
    my ($FieldPos, $Size, $TypePtr, $Skip, $LVer) = @_;
    if($FieldPos==0) {
        return 0;
    }
    
    delete($TypePtr->{"Memb"}{""});
    my $Offset = 0;
    my (%Alignment, %MSize) = ();
    my $MaxAlgn = 0;
    my $End = keys(%{$TypePtr->{"Memb"}})-1;
    my $NextField = $FieldPos+1;
    foreach my $Pos (0 .. $End)
    {
        if($Skip and $Skip->{$Pos})
        { # skip removed/added fields
            if($Pos > $FieldPos)
            { # after the target
                $NextField += 1;
                next;
            }
        }
        ($Alignment{$Pos}, $MSize{$Pos}) = getAlignment($Pos, $TypePtr, $LVer);
        
        if(not $Alignment{$Pos})
        { # emergency exit
            return 0;
        }
        
        if($Alignment{$Pos}>$MaxAlgn) {
            $MaxAlgn = $Alignment{$Pos};
        }
        if($Pos==$FieldPos)
        {
            if($Size==-1)
            { # added/removed fields
                if($Pos!=$End)
                { # skip target field and see
                  # if enough padding will be
                  # created on the next step
                  # to include this field
                    next;
                }
            }
        }
        # padding
        my $Padding = 0;
        if($Offset % $Alignment{$Pos}!=0)
        { # not aligned, add padding
            $Padding = $Alignment{$Pos} - $Offset % $Alignment{$Pos};
        }
        if($Pos==$NextField)
        { # try to place target field in the padding
            if($Size==-1)
            { # added/removed fields
                my $TPadding = 0;
                if($Offset % $Alignment{$FieldPos}!=0)
                {# padding of the target field
                    $TPadding = $Alignment{$FieldPos} - $Offset % $Alignment{$FieldPos};
                }
                if($TPadding+$MSize{$FieldPos}<=$Padding)
                { # enough padding to place target field
                    return 1;
                }
                else {
                    return 0;
                }
            }
            else
            { # changed fields
                my $Delta = $Size-$MSize{$FieldPos};
                if($Delta>=0)
                { # increased
                    if($Size-$MSize{$FieldPos}<=$Padding)
                    { # enough padding to change target field
                        return 1;
                    }
                    else {
                        return 0;
                    }
                }
                else
                { # decreased
                    $Delta = abs($Delta);
                    if($Delta+$Padding>=$MSize{$Pos})
                    { # try to place the next field
                        if(($Offset-$Delta) % $Alignment{$Pos} != 0)
                        { # padding of the next field in new place
                            my $NPadding = $Alignment{$Pos} - ($Offset-$Delta) % $Alignment{$Pos};
                            if($NPadding+$MSize{$Pos}<=$Delta+$Padding)
                            { # enough delta+padding to store next field
                                return 0;
                            }
                        }
                        else
                        {
                            return 0;
                        }
                    }
                    return 1;
                }
            }
        }
        elsif($Pos==$End)
        { # target field is the last field
            if($Size==-1)
            { # added/removed fields
                if($Offset % $MaxAlgn!=0)
                { # tail padding
                    my $TailPadding = $MaxAlgn - $Offset % $MaxAlgn;
                    if($Padding+$MSize{$Pos}<=$TailPadding)
                    { # enough tail padding to place the last field
                        return 1;
                    }
                }
                return 0;
            }
            else
            { # changed fields
                # scenario #1
                my $Offset1 = $Offset+$Padding+$MSize{$Pos};
                if($Offset1 % $MaxAlgn != 0)
                { # tail padding
                    $Offset1 += $MaxAlgn - $Offset1 % $MaxAlgn;
                }
                # scenario #2
                my $Offset2 = $Offset+$Padding+$Size;
                if($Offset2 % $MaxAlgn != 0)
                { # tail padding
                    $Offset2 += $MaxAlgn - $Offset2 % $MaxAlgn;
                }
                if($Offset1!=$Offset2)
                { # different sizes of structure
                    return 0;
                }
                return 1;
            }
        }
        $Offset += $Padding+$MSize{$Pos};
    }
    return 0;
}

sub isScalar($) {
    return ($_[0]=~/\A(unsigned |)(char|short|int|long|long long)\Z/);
}

sub isFloat($) {
    return ($_[0]=~/\A(float|double|long double)\Z/);
}

sub callingConvention_R_Real($)
{
    my $SInfo = $_[0];
    my %Conv = ();
    my %Regs = ();
    my $Hidden = 0;
    foreach my $Elem (keys(%{$SInfo->{"Reg"}}))
    {
        my $Reg = $SInfo->{"Reg"}{$Elem};
        if($Elem eq ".result_ptr")
        {
            $Hidden = 1;
            $Regs{$Reg} = 1;
        }
        elsif(index($Elem, ".result")==0) {
            $Regs{$Reg} = 1;
        }
    }
    if(my @R = sort keys(%Regs))
    {
        $Conv{"Method"} = "reg";
        $Conv{"Registers"} = join(", ", @R);
        if($Hidden) {
            $Conv{"Hidden"} = 1;
        }
    }
    else
    {
        $Conv{"Method"} = "stack";
        $Conv{"Hidden"} = 1;
    }
    return %Conv;
}

sub callingConvention_P_Real($$)
{
    my ($SInfo, $Pos) = @_;
    my %Conv = ();
    my %Regs = ();
    foreach my $Elem (keys(%{$SInfo->{"Reg"}}))
    {
        my $Reg = $SInfo->{"Reg"}{$Elem};
        if($Elem=~/\A$Pos([\.\+]|\Z)/) {
            $Regs{$Reg} = 1;
        }
    }
    if(my @R = sort keys(%Regs))
    {
        $Conv{"Method"} = "reg";
        $Conv{"Registers"} = join(", ", @R);
    }
    else
    {
        $Conv{"Method"} = "stack";
        
        if(defined $SInfo->{"Param"}
        and defined $SInfo->{"Param"}{$Pos})
        {
            if(not defined $SInfo->{"Param"}{$Pos}{"offset"})
            {
                $Conv{"Method"} = "unknown";
            }
        }
    }
    
    return %Conv;
}

return 1;
