1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-05-15 22:06:48 +02:00
David Steele 8efcc38304 Improvements in C codebase:
* Update C naming conventions.
* Use int datatype wherever possible.
* Better separation of C source from Perl interface.
2017-09-30 10:44:03 -04:00

289 lines
10 KiB
Perl

####################################################################################################################################
# Generate C Switch Functions
####################################################################################################################################
package pgBackRestBuild::CodeGen::Switch;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use Storable qw(dclone);
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRestBuild::CodeGen::Common;
####################################################################################################################################
# Cost constants used by optimizer
####################################################################################################################################
# Cost for setting up a new switch statement
use constant COST_SWITCH => 5;
# Cost for each case
use constant COST_SWITCH_CASE => 1;
# Cost for a clause (which might be shared with multipe cases)
use constant COST_SWITCH_CLAUSE => 2;
####################################################################################################################################
# cgenSwitchBuild - build switch statements to perform lookups
####################################################################################################################################
sub cgenSwitchBuild
{
my $strName = shift;
my $strType = shift;
my $ryMatrix = dclone(shift);
my $rstryParam = dclone(shift);
my $rhLabelMap = shift;
my $rhValueLabelMap = shift;
# Build a hash with positions for the data in parameter order
my $rhParamOrder = {};
for (my $iIndex = 0; $iIndex < @{$rstryParam}; $iIndex++)
{
$rhParamOrder->{$rstryParam->[$iIndex]} = $iIndex;
}
# Try each permutation of param order to find the most efficient switch statement
my $iBestCost;
my $strBestSwitch;
foreach my $rstryParamPermute (cgenPermute($rstryParam))
{
# Build a hash with positions for the data in permutation order
my $rhPermuteOrder = {};
for (my $iIndex = 0; $iIndex < @{$rstryParamPermute}; $iIndex++)
{
$rhPermuteOrder->{$rstryParamPermute->[$iIndex]} = $iIndex;
}
# Arrange data in permutation order so later functions don't have to remap for every operation
my @yMatrixPermute;
foreach my $rxyEntry (@{$ryMatrix})
{
my @xyEntryPermute;
for (my $iSwapIdx = 0; $iSwapIdx < @{$rstryParam}; $iSwapIdx++)
{
$xyEntryPermute[$rhPermuteOrder->{$rstryParam->[$iSwapIdx]}] =
$rxyEntry->[$rhParamOrder->{$rstryParam->[$iSwapIdx]}];
}
# Convert the value to a special string if it is null to ease later prcessing
$xyEntryPermute[@{$rstryParam}] = defined($rxyEntry->[-1]) ? $rxyEntry->[-1] : CGEN_DATAVAL_NULL;
push(@yMatrixPermute, \@xyEntryPermute);
}
# Build switch based on current param permutation
my ($strSwitch, $iCost) = cgenSwitchBuildSub(
$strType, \@yMatrixPermute, 0, $rstryParamPermute, $rhLabelMap, $rhValueLabelMap);
# If the switch has a lower cost than the existing one then use it instead
if (!defined($iBestCost) || $iCost < $iBestCost)
{
$iBestCost = $iCost;
$strBestSwitch = $strSwitch;
}
}
# Construct the function based on the best switch statement
return
cgenTypeName($strType) . "\n" .
"${strName}(int " . join(', int ', @{$rstryParam}) . ")\n" .
"{\n" .
"${strBestSwitch}\n" .
"}\n";
}
push @EXPORT, qw(cgenSwitchBuild);
####################################################################################################################################
# cgenSwitchBuildSub - build individual switch statements recursively
####################################################################################################################################
sub cgenSwitchBuildSub
{
my $strType = shift;
my $rstryMatrix = dclone(shift);
my $iDepth = shift;
my $rstryParam = dclone(shift);
my $rhLabelMap = shift;
my $rhValueLabelMap = shift;
my $xMostCommonParentValue = shift;
# How much to indent the code is based on the current depth
my $strIndent = (' ' x (($iDepth * 2) + 1));
# Set initial cost for setting up the switch statement
my $iCost = COST_SWITCH;
# Get the param to be used for the switch statement
my $strParam = shift(@{$rstryParam});
# Determine the most common value
my $iMostCommonTotal = 0;
my $xMostCommonValue;
my $rhMostCommon = {};
foreach my $rxyEntry (@{$rstryMatrix})
{
my $xValue = $rxyEntry->[-1];
$rhMostCommon->{$xValue} = (defined($rhMostCommon->{$xValue}) ? $rhMostCommon->{$xValue} : 0) + 1;
if ($rhMostCommon->{$xValue} > $iMostCommonTotal)
{
$iMostCommonTotal = $rhMostCommon->{$xValue};
$xMostCommonValue = $xValue;
}
}
# Keep going until all keys are exhausted
my $rhClauseHash;
while (@{$rstryMatrix} > 0)
{
# Start processing the first key in the list
my $strKeyTop = $rstryMatrix->[0][0];
# A list of keys values to be passed to the next switch statement
my @stryEntrySub;
# Find all instances of the key and build a hash representing the distinct list of values
my $rhKeyValue = {};
my $iEntryIdx = 0;
while ($iEntryIdx < @{$rstryMatrix})
{
# If this key matches the top key then process
if ($rstryMatrix->[$iEntryIdx][0] eq $strKeyTop)
{
# Add value to unique list
$rhKeyValue->{$rstryMatrix->[$iEntryIdx][-1]} = true;
# Get the key/value entry, remove the top key, and store for the next switch statement
my @stryEntry = @{$rstryMatrix->[$iEntryIdx]};
shift(@stryEntry);
push(@stryEntrySub, \@stryEntry);
# Remove the key from the list
splice(@{$rstryMatrix}, $iEntryIdx, 1);
}
# else move on to the next key
else
{
$iEntryIdx++;
}
};
# Only need a switch if there is more than one value or the one value is not the most common value
if (keys(%{$rhKeyValue}) > 1 || $stryEntrySub[0][-1] ne $xMostCommonValue)
{
my $strClause = '';
$iCost += COST_SWITCH_CASE;
# Process next switch
if (keys(%{$rhKeyValue}) > 1 && @{$stryEntrySub[0]} > 1)
{
my ($strClauseSub, $iCostSub) = cgenSwitchBuildSub(
$strType, \@stryEntrySub, $iDepth + 1, $rstryParam, $rhLabelMap, $rhValueLabelMap, $xMostCommonValue);
$strClause .= $strClauseSub . "\n";
$iCost += $iCostSub;
}
# Return the value
else
{
my $strRetVal = $stryEntrySub[0][-1];
$strClause .=
"${strIndent} return " .
cgenTypeFormat(
$strType,
defined($rhValueLabelMap) && defined($rhValueLabelMap->{$strRetVal}) ?
$rhValueLabelMap->{$strRetVal} : $strRetVal) .
";\n";
}
# Store the key and the clause in a hash to deduplicate
push(@{$rhClauseHash->{$strClause}{key}}, int($strKeyTop));
}
}
# Reorder clause based on an numerical/alpha representation of the case statements. This is done for primarily for readability
# but may have some optimization benefits since integer keys are ordered numerically.
my $rhClauseOrderedHash;
foreach my $strClause (sort(keys(%{$rhClauseHash})))
{
my $strKey;
foreach my $iKey (@{$rhClauseHash->{$strClause}{key}})
{
$strKey .= (defined($strKey) ? '' : ',') . sprintf('%07d', $iKey);
}
$rhClauseOrderedHash->{$strKey}{clause} = $strClause;
$rhClauseOrderedHash->{$strKey}{key} = $rhClauseHash->{$strClause}{key};
}
# Build the switch statement
my $bFirst = true;
my $strFunction =
"${strIndent}switch (${strParam})\n" .
"${strIndent}{\n";
# Retrieve each unique clause and create a case for each key assocated with it
foreach my $strKey (sort(keys(%{$rhClauseOrderedHash})))
{
if (!$bFirst)
{
$strFunction .= "\n";
}
foreach my $strKey (@{$rhClauseOrderedHash->{$strKey}{key}})
{
$iCost += COST_SWITCH_CLAUSE;
$strFunction .=
"${strIndent} case " .
(defined($rhLabelMap->{$strParam}) ? $rhLabelMap->{$strParam}{int($strKey)} : int($strKey)) . ":\n";
}
$strFunction .= $rhClauseOrderedHash->{$strKey}{clause};
$bFirst = false;
}
$strFunction .=
"${strIndent}}\n\n";
# If the most common value is the same as the parent then break instead of returning the same value. Returning the same value
# again might be slightly more efficient but the break makes it easier to debug where values are coming from.
if (defined($xMostCommonParentValue) && $xMostCommonValue eq $xMostCommonParentValue)
{
$strFunction .=
"${strIndent}break;";
}
# Else return the most common value
else
{
$strFunction .=
"${strIndent}return " .
cgenTypeFormat(
$strType,
defined($rhValueLabelMap) && defined($rhValueLabelMap->{$xMostCommonValue}) ?
$rhValueLabelMap->{$xMostCommonValue} : $xMostCommonValue) .
";";
}
return $strFunction, $iCost;
}
1;