diff options
| author | ruki <[email protected]> | 2024-02-24 22:09:03 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-02-24 22:09:03 +0800 |
| commit | ee93d3b328bf2e22896ce1864f63771b920b7bef (patch) | |
| tree | 7f018b6485368eb77dd2c759de9087967b95ae27 | |
| parent | a91f084c0227f3a0ac98964d4676de321ab10219 (diff) | |
| parent | fa62c88a428f8af413eaf71e0acf6f4df0cf3b24 (diff) | |
Merge pull request #4767 from SirLynix/patch-8
Update gas-preprocessor.pl
| -rwxr-xr-x | xmake/scripts/gas-preprocessor.pl | 356 |
1 files changed, 287 insertions, 69 deletions
diff --git a/xmake/scripts/gas-preprocessor.pl b/xmake/scripts/gas-preprocessor.pl index 7e0857a66..ba756119d 100755 --- a/xmake/scripts/gas-preprocessor.pl +++ b/xmake/scripts/gas-preprocessor.pl @@ -16,6 +16,7 @@ my %canonical_arch = ("aarch64" => "aarch64", "arm64" => "aarch64", my %comments = ("aarch64" => '//', "arm" => '@', + "ppc" => '#', "powerpc" => '#'); my @gcc_cmd; @@ -27,6 +28,7 @@ my $as_type = "apple-gas"; my $fix_unreq = $^O eq "darwin"; my $force_thumb = 0; +my $verbose = 0; my $arm_cond_codes = "eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo"; @@ -34,7 +36,7 @@ my $usage_str = " $0\n Gas-preprocessor.pl converts assembler files using modern GNU as syntax for Apple's ancient gas version or clang's incompatible integrated assembler. The -conversion is regularly tested for Libav, x264 and vlc. Other projects might +conversion is regularly tested for FFmpeg, Libav, x264 and vlc. Other projects might use different features which are not correctly handled. Options for this program needs to be separated with ' -- ' from the assembler @@ -48,6 +50,7 @@ command. Following options are currently supported: -force-thumb - assemble as thumb regardless of the input source (note, this is incomplete and only works for sources it explicitly was tested with) + -verbose - print executed commands "; sub usage() { @@ -61,21 +64,23 @@ while (@ARGV) { $fix_unreq = $1 ne "no-"; } elsif ($opt eq "-force-thumb") { $force_thumb = 1; + } elsif ($opt eq "-verbose") { + $verbose = 1; } elsif ($opt eq "-arch") { $arch = shift; - die "unknown arch: '$arch'\n" if not exists $comments{$arch}; + die "unknown arch: '$arch'\n" if not exists $canonical_arch{$arch}; } elsif ($opt eq "-as-type") { $as_type = shift; - die "unknown as type: '$as_type'\n" if $as_type !~ /^((apple-)?(gas|clang)|armasm)$/; + die "unknown as type: '$as_type'\n" if $as_type !~ /^((apple-)?(gas|clang|llvm_gcc)|armasm)$/; } elsif ($opt eq "-help") { usage(); exit 0; } elsif ($opt eq "--" ) { - @gcc_cmd = @ARGV; + @gcc_cmd = @ARGV; } elsif ($opt =~ /^-/) { die "option '$opt' is not known. See '$0 -help' for usage information\n"; } else { - push @gcc_cmd, $opt, @ARGV; + push @gcc_cmd, $opt, @ARGV; } last if (@gcc_cmd); } @@ -86,10 +91,11 @@ if (grep /\.c$/, @gcc_cmd) { } elsif (grep /\.[sS]$/, @gcc_cmd) { # asm file, just do C preprocessor @preprocess_c_cmd = (@gcc_cmd, "-E"); -} elsif (grep /-(v|-help|-version|dumpversion)/, @gcc_cmd) { +} elsif (grep /-(v|h|-version|dumpversion)/, @gcc_cmd) { # pass -v/--version along, used during probing. Matching '-v' might have # uninteded results but it doesn't matter much if gas-preprocessor or # the compiler fails. + print STDERR join(" ", @gcc_cmd)."\n" if $verbose; exec(@gcc_cmd); } else { die "Unrecognized input filetype"; @@ -98,7 +104,6 @@ if ($as_type eq "armasm") { $preprocess_c_cmd[0] = "cpp"; - @preprocess_c_cmd = grep ! /^-nologo$/, @preprocess_c_cmd; # Remove -ignore XX parameter pairs from preprocess_c_cmd my $index = 1; while ($index < $#preprocess_c_cmd) { @@ -109,9 +114,23 @@ if ($as_type eq "armasm") { $index++; } if (grep /^-MM$/, @preprocess_c_cmd) { + push(@preprocess_c_cmd, "-D_WIN32"); + # Normally a preprocessor for windows would predefine _WIN32, + # but we're using any generic system-agnostic preprocessor "cpp" + # with -undef (to avoid getting predefined variables from the host + # system in cross compilation cases), so manually define it here. + # We only use this generic preprocessor for generating dependencies, + # if the build system runs preprocessing with -M/-MM without -MF. + push(@preprocess_c_cmd, "-undef"); + @preprocess_c_cmd = grep ! /^-nologo$/, @preprocess_c_cmd; + print STDERR join(" ", @preprocess_c_cmd)."\n" if $verbose; system(@preprocess_c_cmd) == 0 or die "Error running preprocessor"; exit 0; } + + # If not preprocessing for getting a dependency list, use cl.exe + # instead. + $preprocess_c_cmd[0] = "cl"; } # if compiling, avoid creating an output file named '-.o' @@ -126,13 +145,12 @@ if ((grep /^-c$/, @gcc_cmd) && !(grep /^-o/, @gcc_cmd)) { } } } -# replace only the '-o' argument with '-', avoids rewriting the make dependency -# target specified with -MT to '-' +# Remove the -o argument; if omitted, we by default preprocess to stdout. my $index = 1; while ($index < $#preprocess_c_cmd) { if ($preprocess_c_cmd[$index] eq "-o") { - $index++; - $preprocess_c_cmd[$index] = "-"; + splice(@preprocess_c_cmd, $index, 2); + last; } $index++; } @@ -140,6 +158,10 @@ while ($index < $#preprocess_c_cmd) { my $tempfile; if ($as_type ne "armasm") { @gcc_cmd = map { /\.[csS]$/ ? qw(-x assembler -) : $_ } @gcc_cmd; + + # Filter out options that can cause warnings due to unused arguments, + # Clang warns about unused -D parameters when invoked with "-x assembler". + @gcc_cmd = grep ! /^-D/, @gcc_cmd; } else { @preprocess_c_cmd = grep ! /^-c$/, @preprocess_c_cmd; @preprocess_c_cmd = grep ! /^-m/, @preprocess_c_cmd; @@ -150,6 +172,10 @@ if ($as_type ne "armasm") { @preprocess_c_cmd = grep ! /^-fp/, @preprocess_c_cmd; @preprocess_c_cmd = grep ! /^-EHsc$/, @preprocess_c_cmd; @preprocess_c_cmd = grep ! /^-O/, @preprocess_c_cmd; + @preprocess_c_cmd = grep ! /^-oldit/, @preprocess_c_cmd; + @preprocess_c_cmd = grep ! /^-FS/, @preprocess_c_cmd; + @preprocess_c_cmd = grep ! /^-w/, @preprocess_c_cmd; + @preprocess_c_cmd = grep ! /^-M/, @preprocess_c_cmd; @gcc_cmd = grep ! /^-G/, @gcc_cmd; @gcc_cmd = grep ! /^-W/, @gcc_cmd; @@ -157,6 +183,8 @@ if ($as_type ne "armasm") { @gcc_cmd = grep ! /^-fp/, @gcc_cmd; @gcc_cmd = grep ! /^-EHsc$/, @gcc_cmd; @gcc_cmd = grep ! /^-O/, @gcc_cmd; + @gcc_cmd = grep ! /^-FS/, @gcc_cmd; + @gcc_cmd = grep ! /^-w/, @gcc_cmd; my @outfiles = grep /\.(o|obj)$/, @gcc_cmd; $tempfile = $outfiles[0].".asm"; @@ -200,12 +228,14 @@ $comm = ";" if $as_type =~ /armasm/; my %ppc_spr = (ctr => 9, vrsave => 256); +print STDERR join(" ", @preprocess_c_cmd)."\n" if $verbose; open(INPUT, "-|", @preprocess_c_cmd) || die "Error running preprocessor"; if ($ENV{GASPP_DEBUG}) { open(ASMFILE, ">&STDOUT"); } else { if ($as_type ne "armasm") { + print STDERR join(" ", @gcc_cmd)."\n" if $verbose; open(ASMFILE, "|-", @gcc_cmd) or die "Error running assembler"; } else { open(ASMFILE, ">", $tempfile); @@ -243,7 +273,7 @@ my $thumb = 0; my %thumb_labels; my %call_targets; -my %mov32_targets; +my %import_symbols; my %neon_alias_reg; my %neon_alias_type; @@ -264,16 +294,22 @@ if ($force_thumb) { # note that the handling of arguments is probably overly permissive vs. gas # but it should be the same for valid cases while (<INPUT>) { + # remove lines starting with '#', preprocessing is done, '#' at start of + # the line indicates a comment for all supported archs (aarch64, arm, ppc + # and x86). Also strips line number comments but since they are off anyway + # it is no loss. + s/^\s*#.*$//; # remove all comments (to avoid interfering with evaluating directives) s/(?<!\\)$inputcomm.*//x; # Strip out windows linefeeds s/\r$//; - # Strip out line number comments - armasm can handle them in a separate - # syntax, but since the line numbers are off they are only misleading. - s/^#\s+(\d+).*// if $as_type =~ /armasm/; - parse_line($_); + foreach my $subline (split(";", $_)) { + chomp $subline; + parse_line_continued($subline); + } } +parse_line_continued(""); sub eval_expr { my $expr = $_[0]; @@ -297,7 +333,7 @@ sub handle_if { $expr =~ s/\s//g; $result ^= $expr eq ""; } elsif ($type eq "c") { - if ($expr =~ /(.*)\s*,\s*(.*)/) { + if ($expr =~ /(\S*)\s*,\s*(\S*)/) { $result ^= $1 eq $2; } else { die "argument to .ifc not recognized"; @@ -356,18 +392,32 @@ sub parse_if_line { return 0; } +my $last_line = ""; +sub parse_line_continued { + my $line = $_[0]; + $last_line .= $line; + if ($last_line =~ /\\$/) { + $last_line =~ s/\\$//; + } else { + # Add newlines at the end of lines after concatenation. + $last_line .= "\n"; + parse_line($last_line); + $last_line = ""; + } +} + sub parse_line { my $line = $_[0]; return if (parse_if_line($line)); if (scalar(@rept_lines) == 0) { - if (/\.macro/) { + if ($line =~ /\.macro/) { $macro_level++; if ($macro_level > 1 && !$current_macro) { die "nested macros but we don't have master macro"; } - } elsif (/\.endm/) { + } elsif ($line =~ /\.endm/) { $macro_level--; if ($macro_level < 0) { die "unmatched .endm"; @@ -393,7 +443,7 @@ sub parse_line { } elsif ($macro_level == 0) { expand_macros($line); } else { - if ($line =~ /\.macro\s+([\d\w\.]+)\s*(.*)/) { + if ($line =~ /\.macro\s+([\d\w\.]+)\s*,?\s*(.*)/) { $current_macro = $1; # commas in the argument list are optional, so only use whitespace as the separator @@ -420,7 +470,7 @@ sub parse_line { sub handle_set { my $line = $_[0]; - if ($line =~ /\.set\s+(.*),\s*(.*)/) { + if ($line =~ /\.(?:set|equ)\s+(\S*)\s*,\s*(.*)/) { $symbols{$1} = eval_expr($2); return 1; } @@ -617,18 +667,26 @@ sub is_arm_register { return 0; } +sub is_aarch64_register { + my $name = $_[0]; + if ($name =~ /^[xw]\d+$/) { + return 1; + } + return 0; +} + sub handle_local_label { my $line = $_[0]; my $num = $_[1]; my $dir = $_[2]; my $target = "$num$dir"; if ($dir eq "b") { - $line =~ s/$target/$last_temp_labels{$num}/g; + $line =~ s/\b$target\b/$last_temp_labels{$num}/g; } else { my $name = "temp_label_$temp_label_next"; $temp_label_next++; push(@{$next_temp_labels{$num}}, $name); - $line =~ s/$target/$name/g; + $line =~ s/\b$target\b/$name/g; } return $line; } @@ -668,9 +726,9 @@ sub handle_serialized_line { } # handle GNU as pc-relative relocations for adrp/add - if ($line =~ /(.*)\s*adrp([\w\s\d]+)\s*,\s*#?:pg_hi21:([^\s]+)/) { + if ($line =~ /(.*)\s*adrp([\w\s\d]+)\s*,\s*#?:pg_hi21:([^\s]+)/ and $as_type =~ /^apple-/) { $line = "$1 adrp$2, ${3}\@PAGE\n"; - } elsif ($line =~ /(.*)\s*add([\w\s\d]+)\s*,([\w\s\d]+)\s*,\s*#?:lo12:([^\s]+)/) { + } elsif ($line =~ /(.*)\s*add([\w\s\d]+)\s*,([\w\s\d]+)\s*,\s*#?:lo12:([^\s]+)/ and $as_type =~ /^apple-/) { $line = "$1 add$2, $3, ${4}\@PAGEOFF\n"; } @@ -696,7 +754,7 @@ sub handle_serialized_line { my $cond = $3; my $label = $4; # Don't interpret e.g. bic as b<cc> with ic as conditional code - if ($cond =~ /|$arm_cond_codes/) { + if ($cond =~ /^(|$arm_cond_codes)$/) { if (exists $thumb_labels{$label}) { print ASMFILE ".thumb_func $label\n"; } else { @@ -776,24 +834,25 @@ sub handle_serialized_line { if ($arch eq "aarch64") { # fix missing aarch64 instructions in Xcode 5.1 (beta3) # mov with vector arguments is not supported, use alias orr instead - if ($line =~ /^\s*mov\s+(v\d[\.{}\[\]\w]+),\s*(v\d[\.{}\[\]\w]+)\b\s*$/) { - $line = " orr $1, $2, $2\n"; + if ($line =~ /^(\d+:)?\s*mov\s+(v\d[\.{}\[\]\w]+),\s*(v\d[\.{}\[\]\w]+)\b\s*$/) { + $line = "$1 orr $2, $3, $3\n"; } # movi 16, 32 bit shifted variant, shift is optional - if ($line =~ /^\s*movi\s+(v[0-3]?\d\.(?:2|4|8)[hsHS])\s*,\s*(#\w+)\b\s*$/) { - $line = " movi $1, $2, lsl #0\n"; + if ($line =~ /^(\d+:)?\s*movi\s+(v[0-3]?\d\.(?:2|4|8)[hsHS])\s*,\s*(#\w+)\b\s*$/) { + $line = "$1 movi $2, $3, lsl #0\n"; } # Xcode 5 misses the alias uxtl. Replace it with the more general ushll. # Clang 3.4 misses the alias sxtl too. Replace it with the more general sshll. - if ($line =~ /^\s*(s|u)xtl(2)?\s+(v[0-3]?\d\.[248][hsdHSD])\s*,\s*(v[0-3]?\d\.(?:2|4|8|16)[bhsBHS])\b\s*$/) { - $line = " $1shll$2 $3, $4, #0\n"; + # armasm64 also misses these instructions. + if ($line =~ /^(\d+:)?\s*(s|u)xtl(2)?\s+(v[0-3]?\d\.[248][hsdHSD])\s*,\s*(v[0-3]?\d\.(?:2|4|8|16)[bhsBHS])\b\s*$/) { + $line = "$1 $2shll$3 $4, $5, #0\n"; } - # clang 3.4 does not automatically use shifted immediates in add/sub - if ($as_type eq "clang" and - $line =~ /^(\s*(?:add|sub)s?) ([^#l]+)#([\d\+\-\*\/ <>]+)\s*$/) { - my $imm = eval $3; + # clang 3.4 and armasm64 do not automatically use shifted immediates in add/sub + if (($as_type eq "clang" or $as_type eq "armasm") and + $line =~ /^(\d+:)?(\s*(?:add|sub)s?) ([^#l]+)#([\d\+\-\*\/ <>]+)\s*$/) { + my $imm = eval $4; if ($imm > 4095 and not ($imm & 4095)) { - $line = "$1 $2#" . ($imm >> 12) . ", lsl #12\n"; + $line = "$1 $2 $3#" . ($imm >> 12) . ", lsl #12\n"; } } if ($ENV{GASPP_FIX_XCODE5}) { @@ -824,7 +883,7 @@ sub handle_serialized_line { $labels_seen{$1} = 1; } - if ($line =~ s/^(\d+)://) { + if ($line =~ s/^\s*(\d+)://) { # Convert local labels into unique labels. armasm (at least in # RVCT) has something similar, but still different enough. # By converting to unique labels we avoid any possible @@ -844,7 +903,7 @@ sub handle_serialized_line { $last_temp_labels{$num} = $name; } - if ($line =~ s/^(\w+):/$1/) { + if ($line =~ s/^\s*(\w+):/$1/) { # Skip labels that have already been declared with a PROC, # labels must not be declared multiple times. return if (defined $labels_seen{$1}); @@ -857,21 +916,40 @@ sub handle_serialized_line { # Check branch instructions - if ($line =~ /(?:^|\n)\s*(\w+\s*:\s*)?(bl?x?(..)?(\.w)?)\s+(\w+)/) { + if ($line =~ /(?:^|\n)\s*(\w+\s*:\s*)?(bl?x?\.?([^\s]{2})?(\.w)?)\s+(\w+)/) { my $instr = $2; my $cond = $3; my $width = $4; my $target = $5; # Don't interpret e.g. bic as b<cc> with ic as conditional code - if ($cond !~ /|$arm_cond_codes/) { + if ($cond !~ /^(|$arm_cond_codes)$/) { # Not actually a branch - } elsif ($target =~ /(\d+)([bf])/) { + } elsif ($target =~ /^(\d+)([bf])$/) { + # The target is a local label + $line = handle_local_label($line, $1, $2); + $line =~ s/\b$instr\b/$&.w/ if $width eq "" and $arch eq "arm"; + } elsif (($arch eq "arm" and !is_arm_register($target)) or + ($arch eq "aarch64" and !is_aarch64_register($target))) { + $call_targets{$target}++; + } + } elsif ($line =~ /(?:^|\n)\s*(\w+\s*:\s*)?(cbn?z|adr|tbn?z)\s+(\w+)\s*,(\s*#\d+\s*,)?\s*(\w+)/) { + my $instr = $2; + my $reg = $3; + my $bit = $4; + my $target = $5; + if ($target =~ /^(\d+)([bf])$/) { # The target is a local label $line = handle_local_label($line, $1, $2); - $line =~ s/\b$instr\b/$&.w/ if $width eq ""; - } elsif (!is_arm_register($target)) { + } else { $call_targets{$target}++; } + # Convert tbz with a wX register into an xX register, + # due to armasm64 bugs/limitations. + if (($instr eq "tbz" or $instr eq "tbnz") and $reg =~ /w\d+/) { + my $xreg = $reg; + $xreg =~ s/w/x/; + $line =~ s/\b$reg\b/$xreg/; + } } elsif ($line =~ /^\s*.h?word.*\b\d+[bf]\b/) { while ($line =~ /\b(\d+)([bf])\b/g) { $line = handle_local_label($line, $1, $2); @@ -879,12 +957,12 @@ sub handle_serialized_line { } # ALIGN in armasm syntax is the actual number of bytes - if ($line =~ /\.align\s+(\d+)/) { + if ($line =~ /\.(?:p2)?align\s+(\d+)/) { my $align = 1 << $1; - $line =~ s/\.align\s(\d+)/ALIGN $align/; + $line =~ s/\.(?:p2)?align\s+(\d+)/ALIGN $align/; } # Convert gas style [r0, :128] into armasm [r0@128] alignment specification - $line =~ s/\[([^\[]+),\s*:(\d+)\]/[$1\@$2]/g; + $line =~ s/\[([^\[,]+),?\s*:(\d+)\]/[$1\@$2]/g; # armasm treats logical values {TRUE} and {FALSE} separately from # numeric values - logical operators and values can't be intermixed @@ -909,19 +987,127 @@ sub handle_serialized_line { $line =~ s/\(\s*(\d+)\s*([<>])\s*(\d+)\s*\)/$val/; } - # Change a movw... #:lower16: into a mov32 pseudoinstruction - $line =~ s/^(\s*)movw(\s+\w+\s*,\s*)\#:lower16:(.*)$/$1mov32$2$3/; - # and remove the following, matching movt completely - $line =~ s/^\s*movt\s+\w+\s*,\s*\#:upper16:.*$//; + if ($arch eq "arm") { + # Change a movw... #:lower16: into a mov32 pseudoinstruction + $line =~ s/^(\s*)movw(\s+\w+\s*,\s*)\#:lower16:(.*)$/$1mov32$2$3/; + # and remove the following, matching movt completely + $line =~ s/^\s*movt\s+\w+\s*,\s*\#:upper16:.*$//; - if ($line =~ /^\s*mov32\s+\w+,\s*([a-zA-Z]\w*)/) { - $mov32_targets{$1}++; - } + if ($line =~ /^\s*mov32\s+\w+,\s*([a-zA-Z]\w*)/) { + $import_symbols{$1}++; + } + + # Misc bugs/deficiencies: + # armasm seems unable to parse e.g. "vmov s0, s1" without a type + # qualifier, thus add .f32. + $line =~ s/^(\s+(?:vmov|vadd))(\s+s\d+\s*,\s*s\d+)/$1.f32$2/; + } elsif ($arch eq "aarch64") { + # Convert ext into ext8; armasm64 seems to require it named as ext8. + $line =~ s/^(\s+)ext(\s+)/$1ext8$2/; + + # Pick up targets from ldr x0, =sym+offset + if ($line =~ /^\s*ldr\s+(\w+)\s*,\s*=([a-zA-Z]\w*)(.*)$/) { + my $reg = $1; + my $sym = $2; + my $offset = eval_expr($3); + if ($offset < 0 and $ENV{GASPP_ARMASM64_SKIP_NEG_OFFSET}) { + # armasm64 in VS < 15.6 is buggy with ldr x0, =sym+offset where the + # offset is a negative value; it does write a negative + # offset into the literal pool as it should, but the + # negative offset only covers the lower 32 bit of the 64 + # bit literal/relocation. + # Thus remove the offset and apply it manually with a sub + # afterwards. + $offset = -$offset; + $line = "\tldr $reg, =$sym\n\tsub $reg, $reg, #$offset\n"; + } + $import_symbols{$sym}++; + } + + # armasm64 (currently) doesn't support offsets on adrp targets, + # even though the COFF format relocations (and the linker) + # supports it. Therefore strip out the offsets from adrp and + # add :lo12: (in case future armasm64 would start handling it) + # and add an extra explicit add instruction for the offset. + if ($line =~ s/(adrp\s+\w+\s*,\s*(\w+))([\d\+\-\*\/\(\) <>]+)?/\1/) { + $import_symbols{$2}++; + } + if ($line =~ s/(add\s+(\w+)\s*,\s*\w+\s*,\s*):lo12:(\w+)([\d\+\-\*\/\(\) <>]+)?/\1\3/) { + my $reg = $2; + my $sym = $3; + my $offset = eval_expr($4); + $line .= "\tadd $reg, $reg, #$offset\n" if $offset > 0; + $import_symbols{$sym}++; + } + + # Convert e.g. "add x0, x0, w0, uxtw" into "add x0, x0, w0, uxtw #0", + # or "ldr x0, [x0, w0, uxtw]" into "ldr x0, [x0, w0, uxtw #0]". + $line =~ s/(uxt[whb]|sxt[whb])(\s*\]?\s*)$/\1 #0\2/i; + + # Convert "mov x0, v0.d[0]" into "umov x0, v0.d[0]" + $line =~ s/\bmov\s+[xw]\d+\s*,\s*v\d+\.[ds]/u$&/i; + + # Convert "ccmp w0, #0, #0, ne" into "ccmpne w0, #0, #0", + # and "csel w0, w0, w0, ne" into "cselne w0, w0, w0". + $line =~ s/(ccmp|csel)\s+([xw]\w+)\s*,\s*([xw#]\w+)\s*,\s*([xw#]\w+)\s*,\s*($arm_cond_codes)/\1\5 \2, \3, \4/; + + # Convert "cinc w0, w0, ne" into "cincne w0, w0". + $line =~ s/(cinc)\s+([xw]\w+)\s*,\s*([xw]\w+)\s*,\s*($arm_cond_codes)/\1\4 \2, \3/; - # Misc bugs/deficiencies: - # armasm seems unable to parse e.g. "vmov s0, s1" without a type - # qualifier, thus add .f32. - $line =~ s/^(\s+(?:vmov|vadd))(\s+s)/$1.f32$2/; + # Convert "cset w0, lo" into "csetlo w0" + $line =~ s/(cset)\s+([xw]\w+)\s*,\s*($arm_cond_codes)/\1\3 \2/; + + if ($ENV{GASPP_ARMASM64_SKIP_PRFUM}) { + # Strip out prfum; armasm64 (VS < 15.5) fails to assemble any + # variant/combination of prfum tested so far, but since it is + # a prefetch instruction it can be skipped without changing + # results. + $line =~ s/prfum.*\]//; + } + + # Convert "ldrb w0, [x0, #-1]" into "ldurb w0, [x0, #-1]". + # Don't do this for forms with writeback though. + if ($line =~ /(ld|st)(r[bh]?)\s+(\w+)\s*,\s*\[\s*(\w+)\s*,\s*#([^\]]+)\s*\][^!]/) { + my $instr = $1; + my $suffix = $2; + my $target = $3; + my $base = $4; + my $offset = eval_expr($5); + if ($offset < 0) { + $line =~ s/$instr$suffix/${instr}u$suffix/; + } + } + + if ($ENV{GASPP_ARMASM64_INVERT_SCALE}) { + # Instructions like fcvtzs and scvtf store the scale value + # inverted in the opcode (stored as 64 - scale), but armasm64 + # in VS < 15.5 stores it as-is. Thus convert from + # "fcvtzs w0, s0, #8" into "fcvtzs w0, s0, #56". + if ($line =~ /(?:fcvtzs|scvtf)\s+(\w+)\s*,\s*(\w+)\s*,\s*#(\d+)/) { + my $scale = $3; + my $inverted_scale = 64 - $3; + $line =~ s/#$scale/#$inverted_scale/; + } + } + + # Convert "ld1 {v0.4h-v3.4h}" into "ld1 {v0.4h,v1.4h,v2.4h,v3.4h}" + if ($line =~ /(\{\s*v(\d+)\.(\d+[bhsdBHSD])\s*-\s*v(\d+)\.(\d+[bhsdBHSD])\s*\})/) { + my $regspec = $1; + my $reg1 = $2; + my $layout1 = $3; + my $reg2 = $4; + my $layout2 = $5; + if ($layout1 eq $layout2) { + my $new_regspec = "{"; + foreach my $i ($reg1 .. $reg2) { + $new_regspec .= "," if ($i > $reg1); + $new_regspec .= "v$i.$layout1"; + } + $new_regspec .= "}"; + $line =~ s/$regspec/$new_regspec/; + } + } + } # armasm is unable to parse &0x - add spacing $line =~ s/&0x/& 0x/g; } @@ -930,16 +1116,31 @@ sub handle_serialized_line { # Convert register post indexing to a separate add instruction. # This converts e.g. "ldr r0, [r1], r2" into "ldr r0, [r1]", # "add r1, r1, r2". - $line =~ s/(ldr|str)\s+(\w+),\s*\[(\w+)\],\s*(\w+)/$1 $2, [$3]\n\tadd $3, $3, $4/g; + $line =~ s/((?:ldr|str)[bh]?)\s+(\w+),\s*\[(\w+)\],\s*(\w+)/$1 $2, [$3]\n\tadd $3, $3, $4/g; # Convert "mov pc, lr" into "bx lr", since the former only works # for switching from arm to thumb (and only in armv7), but not # from thumb to arm. - s/mov\s*pc\s*,\s*lr/bx lr/g; + $line =~ s/mov\s*pc\s*,\s*lr/bx lr/g; + + # Convert stmdb/ldmia/stmfd/ldmfd/ldm with only one register into a plain str/ldr with post-increment/decrement. + # Wide thumb2 encoding requires at least two registers in register list while all other encodings support one register too. + $line =~ s/stm(?:db|fd)\s+sp!\s*,\s*\{([^,-]+)\}/str $1, [sp, #-4]!/g; + $line =~ s/ldm(?:ia|fd)?\s+sp!\s*,\s*\{([^,-]+)\}/ldr $1, [sp], #4/g; - # Convert stmdb/ldmia with only one register into a plain str/ldr with post-increment/decrement - $line =~ s/stmdb\s+sp!\s*,\s*\{([^,-]+)\}/str $1, [sp, #-4]!/g; - $line =~ s/ldmia\s+sp!\s*,\s*\{([^,-]+)\}/ldr $1, [sp], #4/g; + # Convert muls into mul+cmp + $line =~ s/muls\s+(\w+),\s*(\w+)\,\s*(\w+)/mul $1, $2, $3\n\tcmp $1, #0/g; + + # Convert "and r0, sp, #xx" into "mov r0, sp", "and r0, r0, #xx" + $line =~ s/and\s+(\w+),\s*(sp|r13)\,\s*#(\w+)/mov $1, $2\n\tand $1, $1, #$3/g; + + # Convert "ldr r0, [r0, r1, lsl #6]" where the shift is >3 (which + # can't be handled in thumb) into "add r0, r0, r1, lsl #6", + # "ldr r0, [r0]", for the special case where the same address is + # used as base and target for the ldr. + if ($line =~ /(ldr[bh]?)\s+(\w+),\s*\[\2,\s*(\w+),\s*lsl\s*#(\w+)\]/ and $4 > 3) { + $line =~ s/(ldr[bh]?)\s+(\w+),\s*\[\2,\s*(\w+),\s*lsl\s*#(\w+)\]/add $2, $2, $3, lsl #$4\n\t$1 $2, [$2]/; + } $line =~ s/\.arm/.thumb/x; } @@ -956,7 +1157,6 @@ sub handle_serialized_line { $line =~ s/\.arch/$comm$&/x if $as_type =~ /^(apple-|clang|armasm)/; $line =~ s/\.object_arch/$comm$&/x if $as_type =~ /^(apple-|armasm)/; $line =~ s/.section\s+.note.GNU-stack.*/$comm$&/x if $as_type =~ /^(apple-|armasm)/; - $line =~ s/\.hidden/$comm$&/x if $as_type =~ /^(apple-|clang|armasm)/; $line =~ s/\.syntax/$comm$&/x if $as_type =~ /armasm/; @@ -970,23 +1170,40 @@ sub handle_serialized_line { $line =~ s/\.int/.long/x; $line =~ s/\.float/.single/x; } + if ($as_type eq "apple-gas") { + $line =~ s/vmrs\s+APSR_nzcv/fmrx r15/x; + } if ($as_type eq "armasm") { $line =~ s/\.global/EXPORT/x; + $line =~ s/\.extern/IMPORT/x; $line =~ s/\.int/dcd/x; $line =~ s/\.long/dcd/x; $line =~ s/\.float/dcfs/x; $line =~ s/\.word/dcd/x; $line =~ s/\.short/dcw/x; $line =~ s/\.byte/dcb/x; + $line =~ s/\.quad/dcq/x; + $line =~ s/\.ascii/dcb/x; + $line =~ s/\.asciz(.*)$/dcb\1,0/x; $line =~ s/\.thumb/THUMB/x; $line =~ s/\.arm/ARM/x; # The alignment in AREA is the power of two, just as .align in gas - $line =~ s/\.text/AREA |.text|, CODE, READONLY, ALIGN=2, CODEALIGN/; - $line =~ s/(\s*)(.*)\.rodata/$1AREA |.rodata|, DATA, READONLY, ALIGN=5/; - + $line =~ s/\.text/AREA |.text|, CODE, READONLY, ALIGN=4, CODEALIGN/; + $line =~ s/(\s*)(.*)\.ro?data(\s*,\s*"\w+")?/$1AREA |.rdata|, DATA, READONLY, ALIGN=5/; + $line =~ s/\.data/AREA |.data|, DATA, ALIGN=5/; + } + if ($as_type eq "armasm" and $arch eq "arm") { $line =~ s/fmxr/vmsr/; $line =~ s/fmrx/vmrs/; - $line =~ s/fadds/vadd/; + $line =~ s/fadds/vadd.f32/; + # Armasm in VS 2019 16.3 errors out on "it" instructions. But + # armasm implicitly adds the necessary it instructions anyway, so we + # can just filter them out. + $line =~ s/^\s*it[te]*\s+/$comm$&/; + } + if ($as_type eq "armasm" and $arch eq "aarch64") { + # Convert "b.eq" into "beq" + $line =~ s/\bb\.($arm_cond_codes)\b/b\1/; } # catch unknown section names that aren't mach-o style (with a comma) @@ -1008,7 +1225,7 @@ if ($as_type ne "armasm") { grep exists $thumb_labels{$_}, keys %call_targets; } else { map print(ASMFILE "\tIMPORT $_\n"), - grep ! exists $labels_seen{$_}, (keys %call_targets, keys %mov32_targets); + grep ! exists $labels_seen{$_}, (keys %call_targets, keys %import_symbols); print ASMFILE "\tEND\n"; } @@ -1016,6 +1233,7 @@ if ($as_type ne "armasm") { close(INPUT) or exit 1; close(ASMFILE) or exit 1; if ($as_type eq "armasm" and ! defined $ENV{GASPP_DEBUG}) { + print STDERR join(" ", @gcc_cmd)."\n" if $verbose; system(@gcc_cmd) == 0 or die "Error running assembler"; } |
