3 # This code is licensed under GPLv2 or later; go to gnu.org to read it
4 # (not that it much matters for an asm preprocessor)
5 # usage: set your assembler to be something like "perl gas-preprocessor.pl gcc"
8 # Apple's gas is ancient and doesn't support modern preprocessing features like
9 # .rept and has ugly macro syntax, among other things. Thus, this script
10 # implements the subset of the gas preprocessor used by x264 and ffmpeg
11 # that isn't supported by Apple's gas.
16 if (grep /\.c$/, @gcc_cmd) {
17 # C file (inline asm?) - compile
18 @preprocess_c_cmd = (@gcc_cmd, "-S");
19 } elsif (grep /\.S$/, @gcc_cmd) {
20 # asm file, just do C preprocessor
21 @preprocess_c_cmd = (@gcc_cmd, "-E");
23 die "Unrecognized input filetype";
25 @gcc_cmd = map { /\.[cS]$/ ? qw(-x assembler -) : $_ } @gcc_cmd;
26 @preprocess_c_cmd = map { /\.o$/ ? "-" : $_ } @preprocess_c_cmd;
28 open(ASMFILE, "-|", @preprocess_c_cmd) || die "Error running preprocessor";
30 my $current_macro = '';
33 my %macro_args_default;
37 # pass 1: parse .macro
38 # note that the handling of arguments is probably overly permissive vs. gas
39 # but it should be the same for valid cases
41 # comment out unsupported directives
44 s/\.endfunc/@.endfunc/x;
49 # the syntax for these is a little different
51 # also catch .section .rodata since the equivalent to .const_data is .section __DATA,__const
52 s/(.*)\.rodata/.const_data/x;
56 # catch unknown section names that aren't mach-o style (with a comma)
57 if (/.section ([^,]*)$/) {
58 die ".section $1 unsupported; figure out the mach-o section name and add it";
61 # macros creating macros is not handled (is that valid?)
62 if (/\.macro\s+([\d\w\.]+)\s*(.*)/) {
65 # commas in the argument list are optional, so only use whitespace as the separator
69 my @args = split(/\s+/, $arglist);
70 foreach my $i (0 .. $#args) {
71 my @argpair = split(/=/, $args[$i]);
72 $macro_args{$current_macro}[$i] = $argpair[0];
73 $argpair[0] =~ s/:vararg$//;
74 $macro_args_default{$current_macro}{$argpair[0]} = $argpair[1];
76 # ensure %macro_lines has the macro name added as a key
77 $macro_lines{$current_macro} = [];
79 if (!$current_macro) {
80 die "ERROR: .endm without .macro";
83 } elsif ($current_macro) {
84 push(@{$macro_lines{$current_macro}}, $_);
92 if ($line =~ /(\S+:|)\s*([\w\d\.]+)\s*(.*)/ && exists $macro_lines{$2}) {
93 push(@pass1_lines, $1);
96 # commas are optional here too, but are syntactically important because
97 # parameters can be blank
98 my @arglist = split(/,/, $3);
101 my @whitespace_split = split(/\s+/, $_);
102 if (!@whitespace_split) {
105 foreach (@whitespace_split) {
114 if ($macro_args_default{$macro}){
115 %replacements = %{$macro_args_default{$macro}};
118 # construct hashtable of text to replace
119 foreach my $i (0 .. $#args) {
120 my $argname = $macro_args{$macro}[$i];
122 if ($args[$i] =~ m/=/) {
123 # arg=val references the argument name
124 # XXX: I'm not sure what the expected behaviour if a lot of
125 # these are mixed with unnamed args
126 my @named_arg = split(/=/, $args[$i]);
127 $replacements{$named_arg[0]} = $named_arg[1];
128 } elsif ($i > $#{$macro_args{$macro}}) {
129 # more args given than the macro has named args
130 # XXX: is vararg allowed on arguments before the last?
131 $argname = $macro_args{$macro}[-1];
132 if ($argname =~ s/:vararg$//) {
133 $replacements{$argname} .= ", $args[$i]";
135 die "Too many arguments to macro $macro";
138 $argname =~ s/:vararg$//;
139 $replacements{$argname} = $args[$i];
143 # apply replacements as regex
144 foreach (@{$macro_lines{$macro}}) {
146 # do replacements by longest first, this avoids wrong replacement
147 # when argument names are subsets of each other
148 foreach (reverse sort {length $a <=> length $b} keys %replacements) {
149 $macro_line =~ s/\\$_/$replacements{$_}/g;
151 $macro_line =~ s/\\\(\)//g; # remove \()
152 expand_macros($macro_line);
155 push(@pass1_lines, $line);
159 close(ASMFILE) or exit 1;
160 open(ASMFILE, "|-", @gcc_cmd) or die "Error running assembler";
166 my %literal_labels; # for ldr <reg>, =<expr>
169 # pass 2: parse .rept and .if variants
170 # NOTE: since we don't implement a proper parser, using .rept with a
171 # variable assigned from .set is not supported
172 foreach my $line (@pass1_lines) {
173 # textual comparison .if
174 # this assumes nothing else on the same line
175 if ($line =~ /\.ifnb\s+(.*)/) {
181 } elsif ($line =~ /\.ifb\s+(.*)/) {
187 } elsif ($line =~ /\.ifc\s+(.*)\s*,\s*(.*)/) {
195 # handle .previous (only with regard to .section not .subsection)
196 if ($line =~ /\.(section|text|const_data)/) {
197 push(@sections, $line);
198 } elsif ($line =~ /\.previous/) {
199 if (!$sections[-2]) {
200 die ".previous without a previous section";
202 $line = $sections[-2];
203 push(@sections, $line);
206 # handle ldr <reg>, =<expr>
207 if ($line =~ /(.*)\s*ldr([\w\s\d]+)\s*,\s*=(.*)/) {
208 my $label = $literal_labels{$3};
210 $label = ".Literal_$literal_num";
212 $literal_labels{$3} = $label;
214 $line = "$1 ldr$2, $label\n";
215 } elsif ($line =~ /\.ltorg/) {
216 foreach my $literal (keys %literal_labels) {
217 $line .= "$literal_labels{$literal}:\n .word $literal\n";
219 %literal_labels = ();
222 # @l -> lo16() @ha -> ha16()
223 $line =~ s/,\s+([^,]+)\@l(\s)/, lo16($1)$2/g;
224 $line =~ s/,\s+([^,]+)\@ha(\s)/, ha16($1)$2/g;
226 if ($line =~ /\.rept\s+(.*)/) {
230 # handle the possibility of repeating another directive on the same line
231 # .endr on the same line is not valid, I don't know if a non-directive is
232 if ($num_repts =~ s/(\.\w+.*)//) {
233 $rept_lines .= "$1\n";
235 $num_repts = eval($num_repts);
236 } elsif ($line =~ /\.endr/) {
237 for (1 .. $num_repts) {
238 print ASMFILE $rept_lines;
241 } elsif ($rept_lines) {
242 $rept_lines .= $line;
248 print ASMFILE ".text\n";
249 foreach my $literal (keys %literal_labels) {
250 print ASMFILE "$literal_labels{$literal}:\n .word $literal\n";
253 close(ASMFILE) or exit 1;