blob: cdeb846b1147caaf3dc192dc5b3efd18c13904e4 [file] [log] [blame]
#!/usr/bin/perl
# **********************************************************
# Copyright (c) 2014-2020 Google, Inc. All rights reserved.
# **********************************************************
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of Google, Inc. nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
# Pass this the file with the op_instr[] encoding chain starting points
# array along with headers containing the table shortcuts and the files
# containing the decoding table files to chain.
# If -o OP_<opc> is passed it will only consider that opcode; else it will
# process all opcodes.
# It will construct each encoding chain and in-place edit the table files.
# It has assumptions on the precise format of the decoding table
# and of the op_instr* starting point array.
#
# To run on one A32 opcode:
#
# tools/arm_table_chain.pl -v -o <opc> core/ir/arm/table_{private,encode,a32}*.[ch]
#
# To run on all A32 opcodes:
#
# tools/arm_table_chain.pl core/ir/arm/table_{private,encode,a32}*.[ch]
#
# To run on one T32 opcode:
#
# tools/arm_table_chain.pl -v -o <opc> core/ir/arm/table_{private,encode,t32}*.[ch]
#
# To run on all T32 opcodes for outside of IT blocks:
#
# tools/arm_table_chain.pl core/ir/arm/table_{private,encode,t32}*.[ch]
#
# To run on all T32 opcodes for inside of IT blocks:
#
# tools/arm_table_chain.pl -it core/ir/arm/table_{private,encode,t32}*.[ch]
#
# Assuming the .n format are always first and thus always the entries,
# we can run arm_table_chain.pl twice for inside and outside of IT blocks
# with T32 tables without affecting the in-table chaining.
my $verbose = 0;
die "Usage: $0 [-o OP_<opcode>] [-it] <table-files>\n" if ($#ARGV < 1);
my $single_op = '';
while ($ARGV[0] eq '-v') {
shift;
$verbose++;
}
if ($ARGV[0] eq '-o') {
shift;
$single_op = shift;
}
if ($ARGV[0] eq '-it') {
$it_block = 1;
shift;
}
my @allfiles = @ARGV;
my $table = "";
my $shape = "";
my $major = 0;
my $minor = 0;
my $instance = 0;
my $t32 = 0;
my @infiles;
# Remove table_t32_16.c or table_t32_16_it.c
foreach $infile (@allfiles) {
if ($it_block) {
push(@infiles, $infile) if ($infile !~/t32_16\.c/);
} else {
push(@infiles, $infile) if ($infile !~/t32_16_it\.c/);
}
}
# Must process headers first
@infiles = sort({return -1 if($a =~ /\.h$/);return 1 if($b =~ /\.h$/); return 0;}
@infiles);
foreach $infile (@infiles) {
print "Processing $infile\n" if ($verbose > 0);
$t32 = 1 if ($infile =~ /_t32_/);
open(INFILE, "< $infile") || die "Couldn't open $file\n";
while (<INFILE>) {
print "xxx $_\n" if ($verbose > 2);
if (/^#define (\w+)\s+\(ptr_int_t\)&(\w+)/) {
$shorthand{$2} = $1;
print "shorthand for $2 = $1\n" if ($verbose > 1);
}
if (/^const instr_info_t (\w+)([^=]+)=/) {
$table = $1;
$shape = $2;
$major = -1;
$minor = 0;
}
if (/{\s*\/\*\s*(\d+)\s*\*\/\s*$/) {
$major++;
$minor = 0;
}
if (/^\s*{(OP_\w+)[ ,]/ && ($single_op eq '' || $single_op eq $1) &&
$1 ne 'OP_CONTD') {
my $opc = $1;
$instance{$opc} = 0 if (!defined($instance{$opc}));
# Ignore duplicate encodings
my $is_new = 1;
my $instance_next = $instance{$opc} + 1;
my $encoding = extract_encoding($_);
my $hex = extract_hex($_);
for (my $i = 0; $i < @{$entry{$opc}}; $i++) {
if ($encoding eq $entry{$opc}[$i]{'encoding'}) {
$is_new = 0;
$dup{$opc}{$encoding} = 1;
# We must place the entry with the fewest required bits into the
# encoding chain, to allow for entries to set distinguishing
# immed and other varying bits on duplicate entries (e.g., b vs f
# for the SIMD D bit) for easier decode table reading.
if (hex($hex) < hex($hex{$opc}{$encoding})) {
$hex{$opc}{$encoding} = $hex;
# Replace the stored instance with this one
$is_new = 1;
$instance_next = $instance{$opc} + 1;
$instance{$opc} = $i;
}
print "Duplicate $hex $encoding => $hex{$opc}{$encoding}\n"
if ($verbose > 1);
last;
}
}
goto dup_line if (!$is_new);
$entry{$opc}[$instance{$opc}]{'line'} = $_;
$entry{$opc}[$instance{$opc}]{'encoding'} = $encoding;
$entry{$opc}[$instance{$opc}]{'hex'} = $hex;
$hex{$opc}{$encoding} = $hex;
die "Error: no shorthand for $table\n" if ($shorthand{$table} eq '');
if ($shape =~ /\d/) {
$entry{$opc}[$instance{$opc}]{'addr_short'} =
sprintf "$shorthand{$table}\[$major][0x%02x]", $minor;
$entry{$opc}[$instance{$opc}]{'addr_long'} =
sprintf "$table\[$major][0x%02x]", $minor;
} else {
$entry{$opc}[$instance{$opc}]{'addr_short'} =
sprintf "$shorthand{$table}\[0x%02x]", $minor;
$entry{$opc}[$instance{$opc}]{'addr_long'} =
sprintf "$table\[0x%02x]", $minor;
}
# Order for sorting:
# + Prefer no-shift over shift
# + Prefer shift via immed over shift via reg
# + Prefer P=1 and U=1
# + Prefer 8-byte over 16-byte and over 4-byte
my $priority = $instance{$opc};
$priority-=10 if (/sh2, i/);
$priority-=10 if (/sh2, R/);
$priority-=10 if (/xop_shift/);
$priority+=10 if (/, [in]/);
$priority+=10 if (/, M.\d/);
$priority-=10 if (/, M.S/);
$priority+=50 if (/PUW=1../);
$priority+=100 if (/PUW=1.0/);
$priority+=10 if (/PUW=.1./);
$priority+=10 if (/[VW][ABC]q,/);
$priority-=50 if (/[VW][ABC]d,/);
# exop must be final member of chain
$priority-=1000 if (/exop\[\w+\]},/);
# + Prefer 16-bit Thumb code over 32-bit Thumb code
$priority+=500 if (/0x([\da-f]){4},/);
$priority+=10 if (/\bSPw\b|\bPCw\b/);
$priority+=10 if (/\bMSPP8w\b|\bMPCP8w\b/);
$priority+=10 if (/\bMSPDBl\b|\bMSPl\b/);
# + We have to take PC special-case first, b/c in some cases
# it has a larger immed which conflicts w/ other bits in non-PC
# (e.g., T32 OP_ldr with MPCN12w opnd). I don't think this
# generalization of that one known case as any negatives.
$priority+=200 if ($table =~ /PC$/);
$entry{$opc}[$instance{$opc}]{'priority'} = $priority;
if ($verbose > 0) {
my $tmp = $entry{$opc}[$instance{$opc}]{'addr_long'};
print "$priority $tmp $_";
}
$instance{$opc} = $instance_next;
}
dup_line:
$minor++ if (/^\s*{[A-Z]/);
}
close(INFILE);
}
foreach my $opc (keys %entry) {
@{$entry{$opc}} = sort({$b->{'priority'} <=> $a->{'priority'}} @{$entry{$opc}});
if ($verbose > 1) {
print "Sorted:\n";
for (my $i = 0; $i < @{$entry{$opc}}; $i++) {
my $tmp = $entry{$opc}[$i]{'addr_long'};
my $pri = $entry{$opc}[$i]{'priority'};
print "$pri $tmp\n";
}
}
}
# Now edit the files in place
$^I = '.bak';
foreach $infile (@infiles) {
@ARGV = ($infile);
while (<>) {
my $encoding = extract_encoding($_);
my $hex = extract_hex($_);
my $handled = 0;
if (/^\s+\/\* (OP_\w+)[ ,]/ && ($single_op eq '' || $single_op eq $1)) {
my $opc = $1;
if (defined($entry{$opc}[0]{'addr_long'})) {
my $start = $entry{$opc}[0]{'addr_long'};
if ($t32) {
if ($it_block) {
# Assuming the .n format are always first and thus always
# the entries, we can implement here for IT block and
# not inside the in-table chaining.
if ($opc !~ /OP_cps/ && $opc !~ /OP_setend$/) {
# OP_cps* and OP_setend are not permitted in IT block.
# We exclude OP_cps* and OP_setend to avoid creating
# separate table for T32.32 IT block instructions.
s/{(&.*,\s*&.*,\s*)&.*}/{\1&$start}/;
}
} else {
s/{(&.*,\s*)&.*(,\s*&.*)}/{\1&$start\2}/;
}
} else {
s/{&.*,(\s*&.*\s*&.*)}/{&$start,\1}/;
}
}
}
if (/^\s*{(OP_\w+)[ ,]/ && ($single_op eq '' || $single_op eq $1) &&
$1 ne 'OP_CONTD') {
my $opc = $1;
if (defined($dup{$opc}{$encoding})) {
# Keep the one with the fewest bits, to allow for optional bits
# (e.g., immeds or reg bits) to be set to make the tables easier to
# read.
# For duplicate hex values, take 1st one.
if ($hex eq $hex{$opc}{$encoding} &&
!defined($printed_dup{$opc}{$encoding})) {
$printed_dup{$opc}{$encoding} = 1;
if (@{$entry{$opc}} == 1) {
s/, *[\w\[\]_]+},/, END_LIST},/ unless /exop\[\w+\]},/;
$handled = 1;
}
} else {
s/, *[\w\[\]_]+},/, DUP_ENTRY},/ unless /exop\[\w+\]},/;
$handled = 1;
}
}
if (!$handled) {
for (my $i = 0; $i < @{$entry{$opc}}; $i++) {
if ($_ eq $entry{$opc}[$i]{'line'}) {
if ($i == @{$entry{$opc}} - 1) {
s/, *[\w\[\]_]+},/, END_LIST},/ unless /exop\[\w+\]},/;
} else {
if (/exop\[\w+\]},/) {
print STDERR
"ERROR: exop must be final element in chain: $_\n";
} else {
my $chain = $entry{$opc}[$i+1]{'addr_short'};
s/, *[\w\[\]_]+},/, $chain},/;
}
}
last;
}
}
}
}
print;
}
}
sub extract_encoding($)
{
my ($line) = @_;
# Remove up through opcode name (to remove encoding hexes) and
# final field and comments
$line =~ s/^[^"]+"/"/;
$line =~ s/,[^,]+}.*$//;
$line =~ s/\s*$//;
return $line;
}
sub extract_hex($)
{
my ($line) = @_;
if ($line =~ /{OP_\w+,\s*0x([0-9a-fA-F]+),/) {
return $1;
} else {
return "not found";
}
}