]> git.sesse.net Git - x264/blob - extras/gas-preprocessor.pl
Detect Avisynth initialization failures
[x264] / extras / gas-preprocessor.pl
1 #!/usr/bin/env perl
2 # by David Conrad
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"
6 use strict;
7
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.
12
13 my @gcc_cmd = @ARGV;
14 my @preprocess_c_cmd;
15
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");
22 } else {
23     die "Unrecognized input filetype";
24 }
25 @gcc_cmd = map { /\.[cS]$/ ? qw(-x assembler -) : $_ } @gcc_cmd;
26 @preprocess_c_cmd = map { /\.o$/ ? "-" : $_ } @preprocess_c_cmd;
27
28 open(ASMFILE, "-|", @preprocess_c_cmd) || die "Error running preprocessor";
29
30 my $current_macro = '';
31 my %macro_lines;
32 my %macro_args;
33 my %macro_args_default;
34
35 my @pass1_lines;
36
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
40 while (<ASMFILE>) {
41     # comment out unsupported directives
42     s/\.type/@.type/x;
43     s/\.func/@.func/x;
44     s/\.endfunc/@.endfunc/x;
45     s/\.ltorg/@.ltorg/x;
46     s/\.size/@.size/x;
47     s/\.fpu/@.fpu/x;
48
49     # the syntax for these is a little different
50     s/\.global/.globl/x;
51     # also catch .section .rodata since the equivalent to .const_data is .section __DATA,__const
52     s/(.*)\.rodata/.const_data/x;
53     s/\.int/.long/x;
54     s/\.float/.single/x;
55
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";
59     }
60
61     # macros creating macros is not handled (is that valid?)
62     if (/\.macro\s+([\d\w\.]+)\s*(.*)/) {
63         $current_macro = $1;
64
65         # commas in the argument list are optional, so only use whitespace as the separator
66         my $arglist = $2;
67         $arglist =~ s/,/ /g;
68
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];
75         }
76         # ensure %macro_lines has the macro name added as a key
77         $macro_lines{$current_macro} = [];
78     } elsif (/\.endm/) {
79         if (!$current_macro) {
80             die "ERROR: .endm without .macro";
81         }
82         $current_macro = '';
83     } elsif ($current_macro) {
84         push(@{$macro_lines{$current_macro}}, $_);
85     } else {
86         expand_macros($_);
87     }
88 }
89
90 sub expand_macros {
91     my $line = @_[0];
92     if ($line =~ /(\S+:|)\s*([\w\d\.]+)\s*(.*)/ && exists $macro_lines{$2}) {
93         push(@pass1_lines, $1);
94         my $macro = $2;
95
96         # commas are optional here too, but are syntactically important because
97         # parameters can be blank
98         my @arglist = split(/,/, $3);
99         my @args;
100         foreach (@arglist) {
101             my @whitespace_split = split(/\s+/, $_);
102             if (!@whitespace_split) {
103                 push(@args, '');
104             } else {
105                 foreach (@whitespace_split) {
106                     if (length($_)) {
107                         push(@args, $_);
108                     }
109                 }
110             }
111         }
112
113         my %replacements;
114         if ($macro_args_default{$macro}){
115             %replacements = %{$macro_args_default{$macro}};
116         }
117
118         # construct hashtable of text to replace
119         foreach my $i (0 .. $#args) {
120             my $argname = $macro_args{$macro}[$i];
121
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]";
134                 } else {
135                     die "Too many arguments to macro $macro";
136                 }
137             } else {
138                 $argname =~ s/:vararg$//;
139                 $replacements{$argname} = $args[$i];
140             }
141         }
142
143         # apply replacements as regex
144         foreach (@{$macro_lines{$macro}}) {
145             my $macro_line = $_;
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;
150             }
151             $macro_line =~ s/\\\(\)//g;     # remove \()
152             expand_macros($macro_line);
153         }
154     } else {
155         push(@pass1_lines, $line);
156     }
157 }
158
159 close(ASMFILE) or exit 1;
160 open(ASMFILE, "|-", @gcc_cmd) or die "Error running assembler";
161
162 my @sections;
163 my $num_repts;
164 my $rept_lines;
165
166 my %literal_labels;     # for ldr <reg>, =<expr>
167 my $literal_num = 0;
168
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+(.*)/) {
176         if ($1) {
177             $line = ".if 1\n";
178         } else {
179             $line = ".if 0\n";
180         }
181     } elsif ($line =~ /\.ifb\s+(.*)/) {
182         if ($1) {
183             $line = ".if 0\n";
184         } else {
185             $line = ".if 1\n";
186         }
187     } elsif ($line =~ /\.ifc\s+(.*)\s*,\s*(.*)/) {
188         if ($1 eq $2) {
189             $line = ".if 1\n";
190         } else {
191             $line = ".if 0\n";
192         }
193     }
194
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";
201         }
202         $line = $sections[-2];
203         push(@sections, $line);
204     }
205
206     # handle ldr <reg>, =<expr>
207     if ($line =~ /(.*)\s*ldr([\w\s\d]+)\s*,\s*=(.*)/) {
208         my $label = $literal_labels{$3};
209         if (!$label) {
210             $label = ".Literal_$literal_num";
211             $literal_num++;
212             $literal_labels{$3} = $label;
213         }
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";
218         }
219         %literal_labels = ();
220     }
221
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;
225
226     if ($line =~ /\.rept\s+(.*)/) {
227         $num_repts = $1;
228         $rept_lines = "\n";
229
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";
234         }
235         $num_repts = eval($num_repts);
236     } elsif ($line =~ /\.endr/) {
237         for (1 .. $num_repts) {
238             print ASMFILE $rept_lines;
239         }
240         $rept_lines = '';
241     } elsif ($rept_lines) {
242         $rept_lines .= $line;
243     } else {
244         print ASMFILE $line;
245     }
246 }
247
248 print ASMFILE ".text\n";
249 foreach my $literal (keys %literal_labels) {
250     print ASMFILE "$literal_labels{$literal}:\n .word $literal\n";
251 }
252
253 close(ASMFILE) or exit 1;