ftu/blst/asm/arm-xlate.pl

382 lines
9.3 KiB
Perl
Raw Normal View History

2022-09-09 06:47:49 +00:00
#!/usr/bin/env perl
#
# Copyright Supranational LLC
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
# ARM assembler distiller/adapter by \@dot-asm.
use strict;
################################################################
# Recognized "flavour"-s are:
#
# linux[32|64] GNU assembler, effectively pass-through
# ios[32|64] global symbols' decorations, PIC tweaks, etc.
# win[32|64] Visual Studio armasm-specific directives
# coff[32|64] e.g. clang --target=arm-windows ...
#
my $flavour = shift;
$flavour = "linux" if (!$flavour or $flavour eq "void");
my $output = shift;
open STDOUT,">$output" || die "can't open $output: $!";
my %GLOBALS;
my $dotinlocallabels = ($flavour !~ /ios/) ? 1 : 0;
my $in_proc; # used with 'windows' flavour
################################################################
# directives which need special treatment on different platforms
################################################################
my $arch = sub { } if ($flavour !~ /linux|coff64/);# omit .arch
my $fpu = sub { } if ($flavour !~ /linux/); # omit .fpu
my $rodata = sub {
SWITCH: for ($flavour) {
/linux/ && return ".section\t.rodata";
/ios/ && return ".section\t__TEXT,__const";
/coff/ && return ".section\t.rdata,\"dr\"";
/win/ && return "\tAREA\t|.rdata|,DATA,READONLY,ALIGN=8";
last;
}
};
my $hidden = sub {
if ($flavour =~ /ios/) { ".private_extern\t".join(',',@_); }
} if ($flavour !~ /linux/);
my $comm = sub {
my @args = split(/,\s*/,shift);
my $name = @args[0];
my $global = \$GLOBALS{$name};
my $ret;
if ($flavour =~ /ios32/) {
$ret = ".comm\t_$name,@args[1]\n";
$ret .= ".non_lazy_symbol_pointer\n";
$ret .= "$name:\n";
$ret .= ".indirect_symbol\t_$name\n";
$ret .= ".long\t0\n";
$ret .= ".previous";
$name = "_$name";
} elsif ($flavour =~ /win/) {
$ret = "\tCOMMON\t|$name|,@args[1]";
} elsif ($flavour =~ /coff/) {
$ret = ".comm\t$name,@args[1]";
} else {
$ret = ".comm\t".join(',',@args);
}
$$global = $name;
$ret;
};
my $globl = sub {
my $name = shift;
my $global = \$GLOBALS{$name};
my $ret;
SWITCH: for ($flavour) {
/ios/ && do { $name = "_$name"; last; };
/win/ && do { $ret = ""; last; };
}
$ret = ".globl $name" if (!defined($ret));
$$global = $name;
$ret;
};
my $global = $globl;
my $extern = sub {
&$globl(@_);
if ($flavour =~ /win/) {
return "\tEXTERN\t@_";
}
return; # return nothing
};
my $type = sub {
my $arg = join(',',@_);
my $ret;
SWITCH: for ($flavour) {
/ios32/ && do { if ($arg =~ /(\w+),\s*%function/) {
$ret = "#ifdef __thumb2__\n" .
".thumb_func $1\n" .
"#endif";
}
last;
};
/win/ && do { if ($arg =~ /(\w+),\s*%(function|object)/) {
my $type = "[DATA]";
if ($2 eq "function") {
$in_proc = $1;
$type = "[FUNC]";
}
$ret = $GLOBALS{$1} ? "\tEXPORT\t|$1|$type"
: "";
}
last;
};
/coff/ && do { if ($arg =~ /(\w+),\s*%function/) {
$ret = ".def $1;\n".
".type 32;\n".
".endef";
}
last;
};
}
return $ret;
} if ($flavour !~ /linux/);
my $size = sub {
if ($in_proc && $flavour =~ /win/) {
$in_proc = undef;
return "\tENDP";
}
} if ($flavour !~ /linux/);
my $inst = sub {
if ($flavour =~ /win/) { "\tDCDU\t".join(',',@_); }
else { ".long\t".join(',',@_); }
} if ($flavour !~ /linux/);
my $asciz = sub {
my $line = join(",",@_);
if ($line =~ /^"(.*)"$/)
{ if ($flavour =~ /win/) {
"\tDCB\t$line,0\n\tALIGN\t4";
} else {
".byte " . join(",",unpack("C*",$1),0) . "\n.align 2";
}
} else { ""; }
};
my $align = sub {
"\tALIGN\t".2**@_[0];
} if ($flavour =~ /win/);
$align = sub {
".p2align\t".@_[0];
} if ($flavour =~ /coff/);
my $byte = sub {
"\tDCB\t".join(',',@_);
} if ($flavour =~ /win/);
my $short = sub {
"\tDCWU\t".join(',',@_);
} if ($flavour =~ /win/);
my $word = sub {
"\tDCDU\t".join(',',@_);
} if ($flavour =~ /win/);
my $long = $word if ($flavour =~ /win/);
my $quad = sub {
"\tDCQU\t".join(',',@_);
} if ($flavour =~ /win/);
my $skip = sub {
"\tSPACE\t".shift;
} if ($flavour =~ /win/);
my $code = sub {
"\tCODE@_[0]";
} if ($flavour =~ /win/);
my $thumb = sub { # .thumb should appear prior .text in source
"# define ARM THUMB\n" .
"\tTHUMB";
} if ($flavour =~ /win/);
my $text = sub {
"\tAREA\t|.text|,CODE,ALIGN=8,".($flavour =~ /64/ ? "ARM64" : "ARM");
} if ($flavour =~ /win/);
my $syntax = sub {} if ($flavour =~ /win/); # omit .syntax
my $rva = sub {
# .rva directive comes in handy only on 32-bit Windows, i.e. it can
# be used only in '#if defined(_WIN32) && !defined(_WIN64)' sections.
# However! Corresponding compilers don't seem to bet on PIC, which
# raises the question why would assembler programmer have to jump
# through the hoops? But just in case, it would go as following:
#
# ldr r1,.LOPENSSL_armcap
# ldr r2,.LOPENSSL_armcap+4
# adr r0,.LOPENSSL_armcap
# bic r1,r1,#1 ; de-thumb-ify link.exe's ideas
# sub r0,r0,r1 ; r0 is image base now
# ldr r0,[r0,r2]
# ...
#.LOPENSSL_armcap:
# .rva .LOPENSSL_armcap ; self-reference
# .rva OPENSSL_armcap_P ; real target
#
# Non-position-independent [and ISA-neutral] alternative is so much
# simpler:
#
# ldr r0,.LOPENSSL_armcap
# ldr r0,[r0]
# ...
#.LOPENSSL_armcap:
# .long OPENSSL_armcap_P
#
"\tDCDU\t@_[0]\n\tRELOC\t2"
} if ($flavour =~ /win(?!64)/);
################################################################
# some broken instructions in Visual Studio armasm[64]...
my $it = sub {} if ($flavour =~ /win32/); # omit 'it'
my $ext = sub {
"\text8\t".join(',',@_);
} if ($flavour =~ /win64/);
my $csel = sub {
my ($args,$comment) = split(m|\s*//|,shift);
my @regs = split(m|,\s*|,$args);
my $cond = pop(@regs);
"\tcsel$cond\t".join(',',@regs);
} if ($flavour =~ /win64/);
my $csetm = sub {
my ($args,$comment) = split(m|\s*//|,shift);
my @regs = split(m|,\s*|,$args);
my $cond = pop(@regs);
"\tcsetm$cond\t".join(',',@regs);
} if ($flavour =~ /win64/);
# ... then conditional branch instructions are also broken, but
# maintaining all the variants is tedious, so I kludge-fix it
# elsewhere...
################################################################
my $adrp = sub {
my ($args,$comment) = split(m|\s*//|,shift);
"\tadrp\t$args\@PAGE";
} if ($flavour =~ /ios64/);
my $paciasp = sub {
($flavour =~ /linux/) ? "\t.inst\t0xd503233f"
: &$inst(0xd503233f);
};
my $autiasp = sub {
($flavour =~ /linux/) ? "\t.inst\t0xd50323bf"
: &$inst(0xd50323bf);
};
sub range {
my ($r,$sfx,$start,$end) = @_;
join(",",map("$r$_$sfx",($start..$end)));
}
sub expand_line {
my $line = shift;
my @ret = ();
pos($line)=0;
while ($line =~ m/\G[^@\/\{\"]*/g) {
if ($line =~ m/\G(@|\/\/|$)/gc) {
last;
}
elsif ($line =~ m/\G\{/gc) {
my $saved_pos = pos($line);
$line =~ s/\G([rdqv])([0-9]+)([^\-]*)\-\1([0-9]+)\3/range($1,$3,$2,$4)/e;
pos($line) = $saved_pos;
$line =~ m/\G[^\}]*\}/g;
}
elsif ($line =~ m/\G\"/gc) {
$line =~ m/\G[^\"]*\"/g;
}
}
$line =~ s/\b(\w+)/$GLOBALS{$1} or $1/ge;
if ($flavour =~ /win/) {
# adjust alignment hints, "[rN,:32]" -> "[rN@32]"
$line =~ s/(\[\s*(?:r[0-9]+|sp))\s*,?\s*:([0-9]+\s*\])/$1\@$2/;
# adjust local labels, ".Lwhatever" -> "|$Lwhatever|"
$line =~ s/\.(L\w{2,})/|\$$1|/g;
# omit "#:lo12:" on win64
$line =~ s/#:lo12://;
} elsif ($flavour =~ /coff(?!64)/) {
$line =~ s/\.L(\w{2,})/(\$ML$1)/g;
} elsif ($flavour =~ /ios64/) {
$line =~ s/#:lo12:(\w+)/$1\@PAGEOFF/;
}
return $line;
}
while(my $line=<>) {
# fix up assembler-specific commentary delimiter
$line =~ s/@(?=[\s@])/\;/g if ($flavour =~ /win|coff/);
if ($line =~ m/^\s*(#|@|;|\/\/)/) { print $line; next; }
$line =~ s|/\*.*\*/||; # get rid of C-style comments...
$line =~ s|^\s+||; # ... and skip white spaces in beginning...
$line =~ s|\s+$||; # ... and at the end
{
$line =~ s|[\b\.]L(\w{2,})|L$1|g; # common denominator for Locallabel
$line =~ s|\bL(\w{2,})|\.L$1|g if ($dotinlocallabels);
}
{
$line =~ s|(^[\.\w]+)\:\s*||;
my $label = $1;
if ($label) {
$label = ($GLOBALS{$label} or $label);
if ($flavour =~ /win/) {
$label =~ s|^\.L(?=\w)|\$L|;
printf "|%s|%s", $label, ($label eq $in_proc ? " PROC" : "");
} else {
$label =~ s|^\.L(?=\w)|\$ML| if ($flavour =~ /coff(?!64)/);
printf "%s:", $label;
}
}
}
if ($line !~ m/^[#@;]/) {
$line =~ s|^\s*(\.?)(\S+)\s*||;
my $c = $1; $c = "\t" if ($c eq "");
my $mnemonic = $2;
my $opcode;
if ($mnemonic =~ m/([^\.]+)\.([^\.]+)/) {
$opcode = eval("\$$1_$2");
} else {
$opcode = eval("\$$mnemonic");
}
my $arg=expand_line($line);
if (ref($opcode) eq 'CODE') {
$line = &$opcode($arg);
} elsif ($mnemonic) {
if ($flavour =~ /win64/) {
# "b.cond" -> "bcond", kludge-fix:-(
$mnemonic =~ s/^b\.([a-z]{2}$)/b$1/;
}
$line = $c.$mnemonic;
$line.= "\t$arg" if ($arg ne "");
}
}
print $line if ($line);
print "\n";
}
print "\tEND\n" if ($flavour =~ /win/);
close STDOUT;