-d option, classfilecompiler cleanu
[nestedvm.git] / src / org / ibex / nestedvm / Compiler.java
1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
2
3 package org.ibex.nestedvm;
4
5 import java.util.*;
6 import java.io.*;
7
8 import org.ibex.nestedvm.util.*;
9
10 public abstract class Compiler implements Registers {    
11     /** The ELF binary being read */
12     ELF elf;
13     
14     /** The name of the class beging generated */
15     final String fullClassName;
16     
17     /** The name of the binary this class is begin generated from */
18     String source = "unknown.mips.binary";
19     public void setSource(String source) { this.source = source; }
20         
21     /** Thrown when the compilation fails for some reason */
22     static class Exn extends Exception { public Exn(String s) { super(s); } }
23     
24     // Set this to true to enable fast memory access 
25     // When this is enabled a Java RuntimeException will be thrown when a page fault occures. When it is disabled
26     // a FaultException will be throw which is easier to catch and deal with, however. as the name implies, this is slower
27     boolean fastMem = true;
28     
29     // This MUST be a power of two. If it is not horrible things will happen
30     // NOTE: This value can be much higher without breaking the classfile 
31     // specs (around 1024) but Hotstop seems to do much better with smaller
32     // methods. 
33     int maxInsnPerMethod = 128;
34     
35     // non-configurable
36     int maxBytesPerMethod;
37     int methodMask;
38     int methodShift;
39     void maxInsnPerMethodInit() throws Exn {
40         if((maxInsnPerMethod&(maxInsnPerMethod-1)) != 0) throw new Exn("maxBytesPerMethod is not a power of two");
41         maxBytesPerMethod = maxInsnPerMethod*4;
42         methodMask = ~(maxBytesPerMethod-1);
43         while(maxBytesPerMethod>>>methodShift != 1) methodShift++;
44     }
45     
46     // True to try to determine which case statement are needed and only include them
47     boolean pruneCases = true;
48     
49     boolean assumeTailCalls = true;
50     
51     // True to insert some code in the output to help diagnore compiler problems
52     boolean debugCompiler = false;
53     
54     // True to print various statistics about the compilation
55     boolean printStats = false;
56     
57     // True to generate runtime statistics that slow execution down significantly
58     boolean runtimeStats = false;
59     
60     boolean supportCall = true;
61     
62     boolean nullPointerCheck = false;
63     
64     String runtimeClass = "org.ibex.nestedvm.Runtime";
65     
66     String hashClass = "java.util.Hashtable";
67     
68     boolean unixRuntime;
69     
70     boolean lessConstants;
71     
72     boolean singleFloat;
73             
74     int pageSize = 4096;
75     int totalPages = 65536;
76     int pageShift;
77     boolean onePage;
78     
79     void pageSizeInit() throws Exn {
80         if((pageSize&(pageSize-1)) != 0) throw new Exn("pageSize not a multiple of two");
81         if((totalPages&(totalPages-1)) != 0) throw new Exn("totalPages not a multiple of two");
82         while(pageSize>>>pageShift != 1) pageShift++;
83     }
84     
85     /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
86     Hashtable jumpableAddresses;
87     
88     /** Some important symbols */
89     ELF.Symbol userInfo, gp;
90     
91     private static void usage() {
92         System.err.println("Usage: java Compiler [-outfile output.java] [-o options] [-dumpoptions] <classname> <binary.mips>");
93         System.err.println("-o takes mount(8) like options and can be specified multiple times");
94         System.err.println("Available options:");
95         for(int i=0;i<options.length;i+=2)
96             System.err.print(options[i] + ": " + wrapAndIndent(options[i+1],18-2-options[i].length(),18,62));
97         System.exit(1);
98     }
99     
100     public static void main(String[] args) throws IOException {
101         String outfile = null;
102         String outdir = null;
103         String o = null;
104         String className = null;
105         String mipsBinaryFileName = null;
106         String outformat = null;
107         boolean dumpOptions = false;
108         int arg = 0;
109         while(args.length-arg > 0) {
110             if(args[arg].equals("-outfile")) {
111                 arg++;
112                 if(arg==args.length) usage();
113                 outfile = args[arg];
114             } else if(args[arg].equals("-d")) {
115                 arg++;
116                 if(arg==args.length) usage();
117                 outdir = args[arg];
118             } else if(args[arg].equals("-outformat")) {
119                 arg++;
120                 if(arg==args.length) usage();
121                 outformat = args[arg];
122             } else if(args[arg].equals("-o")) {
123                 arg++;
124                 if(arg==args.length) usage();
125                 if(o==null || o.length() == 0)
126                     o = args[arg];
127                 else if(args[arg].length() != 0)
128                     o += "," + args[arg];
129             } else if(args[arg].equals("-dumpoptions")) {
130                 dumpOptions = true;
131             } else if(className == null) {
132                 className = args[arg];
133             } else if(mipsBinaryFileName == null) {
134                 mipsBinaryFileName = args[arg];
135             } else {
136                 usage();
137             }
138             arg++;
139         }
140         if(className == null || mipsBinaryFileName == null) usage();
141         
142         Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
143         
144         Writer w = null;
145         OutputStream os = null;
146         Compiler comp = null;
147         if(outformat == null || outformat.equals("class")) {
148             if(outfile != null) {
149                 os = new FileOutputStream(outfile);
150                 comp = new ClassFileCompiler(mipsBinary,className,os);
151             } else if(outdir != null) {
152                 File f = new File(outdir);
153                 if(!f.isDirectory()) {
154                     System.err.println(outdir + " doesn't exist or is not a directory");
155                     System.exit(1);
156                 }
157                 comp = new ClassFileCompiler(mipsBinary,className,f);
158             } else {
159                 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
160                 System.exit(1);
161             }
162         } else if(outformat.equals("javasource") || outformat .equals("java")) {
163             w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
164             comp = new JavaSourceCompiler(mipsBinary,className,w);
165         } else {
166             System.err.println("Unknown output format: " + outformat);
167             System.exit(1);
168         }
169         
170         comp.parseOptions(o);
171         comp.setSource(mipsBinaryFileName);
172         
173         if(dumpOptions) {
174             System.err.println("== Options ==");
175             for(int i=0;i<options.length;i+=2)
176                 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
177             System.err.println("== End Options ==");
178         }
179             
180         try {
181             comp.go();
182         } catch(Exn e) {
183             System.err.println("Compiler Error: " + e.getMessage());
184             System.exit(1);
185         } finally {
186             if(w != null) w.close();
187             if(os != null) os.close();
188         }
189     }
190         
191     public Compiler(Seekable binary, String fullClassName) throws IOException {
192         this.fullClassName = fullClassName;
193         elf = new ELF(binary);
194         
195         if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
196         if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
197         if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
198     }
199
200     abstract void _go() throws Exn, IOException;
201     
202     private boolean used;
203     public void go() throws Exn, IOException {
204         if(used) throw new RuntimeException("Compiler instances are good for one shot only");
205         used = true;
206         
207         if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
208         if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
209         if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
210         if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
211         if(onePage) totalPages = 1;
212
213         maxInsnPerMethodInit();
214         pageSizeInit();
215         
216         // Get a copy of the symbol table in the elf binary
217         ELF.Symtab symtab = elf.getSymtab();
218         if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
219         ELF.Symbol sym;
220         
221         userInfo = symtab.getGlobalSymbol("user_info");
222         gp = symtab.getGlobalSymbol("_gp");
223         if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");   
224         
225         if(pruneCases) {
226             // Find all possible branches
227             jumpableAddresses = new Hashtable();
228             
229             jumpableAddresses.put(new Integer(elf.header.entry),Boolean.TRUE);
230             
231             ELF.SHeader text = elf.sectionWithName(".text");
232             if(text == null) throw new Exn("No .text segment");
233             
234             findBranchesInSymtab(symtab,jumpableAddresses);
235             
236             for(int i=0;i<elf.sheaders.length;i++) {
237                 ELF.SHeader sheader = elf.sheaders[i];
238                 String name = sheader.name;
239                 // if this section doesn't get loaded into our address space don't worry about it
240                 if(sheader.addr == 0x0) continue;
241                 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
242                     findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
243             }
244             
245             findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);            
246         }
247
248         if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
249         
250         for(int i=0;i<elf.sheaders.length;i++) {
251             String name = elf.sheaders[i].name;
252             if(elf.sheaders[i].addr != 0 && !(
253                 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
254                 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
255                     throw new Exn("Unknown section: " + name);
256         }
257         _go();
258     }
259     
260     private void findBranchesInSymtab(ELF.Symtab symtab, Hashtable jumps) {
261         ELF.Symbol[] symbols = symtab.symbols;
262         int n=0;
263         for(int i=0;i<symbols.length;i++) {
264             ELF.Symbol s = symbols[i];
265             if(s.type == ELF.Symbol.STT_FUNC) {
266                 if(jumps.put(new Integer(s.addr),Boolean.TRUE) == null) {
267                     //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
268                     n++;
269                 }
270             }
271         }
272         if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
273     }
274     
275     private void findBranchesInText(int base, DataInputStream dis, int size, Hashtable jumps) throws IOException {
276         int count = size/4;
277         int pc = base;
278         int n=0;
279         int[] lui_val = new int[32];
280         int[] lui_pc = new int[32];
281         //Interpreter inter = new Interpreter(source);
282         
283         for(int i=0;i<count;i++,pc+=4) {
284             int insn = dis.readInt();
285             int op = (insn >>> 26) & 0xff; 
286             int rs = (insn >>> 21) & 0x1f;
287             int rt = (insn >>> 16) & 0x1f;
288             int signedImmediate = (insn << 16) >> 16;
289             int unsignedImmediate = insn & 0xffff;
290             int branchTarget = signedImmediate;
291             int jumpTarget = (insn & 0x03ffffff);
292             int subcode = insn & 0x3f;
293             
294             switch(op) {
295                 case 0:
296                     switch(subcode) {
297                         case 9: // JALR
298                             if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
299                             break;
300                         case 12: // SYSCALL
301                             if(jumps.put(new Integer(pc+4),Boolean.TRUE) == null) n++; 
302                             break;
303                     }
304                     break;
305                 case 1:
306                     switch(rt) {
307                         case 16: // BLTZAL
308                         case 17: // BGTZAL
309                             if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
310                             // fall through
311                         case 0: // BLTZ
312                         case 1: // BGEZ
313                             if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
314                             break;
315                     }
316                     break;
317                 case 3: // JAL
318                     if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
319                     // fall through
320                 case 2: // J
321                     if(jumps.put(new Integer((pc&0xf0000000)|(jumpTarget << 2)),Boolean.TRUE) == null) n++;
322                     break;
323                 case 4: // BEQ
324                 case 5: // BNE
325                 case 6: // BLEZ
326                 case 7: // BGTZ
327                     if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
328                     break;
329                 case 9: { // ADDIU
330                     if(pc - lui_pc[rs] <= 4*32) {
331                         int t = (lui_val[rs]<<16)+signedImmediate;
332                         if((t&3)==0 && t >= base && t < base+size) {
333                             if(jumps.put(new Integer(t),Boolean.TRUE) == null) {
334                                 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
335                                 n++;
336                             }
337                         }
338                         // we just blew it away
339                         if(rt == rs) lui_pc[rs] = 0;
340                     }
341                     break;
342                 }
343                 case 15: { // LUI
344                     lui_val[rt] = unsignedImmediate;
345                     lui_pc[rt] = pc;
346                     break;
347                 }
348                     
349                 case 17: // FPU Instructions
350                     switch(rs) {
351                         case 8: // BC1F, BC1T
352                             if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
353                             break;
354                     }
355                     break;
356             }
357         }
358         dis.close();
359         if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
360     }
361     
362     private void findBranchesInData(DataInputStream dis, int size, Hashtable jumps, int textStart, int textEnd) throws IOException {
363         int count = size/4;
364         int n=0;
365         for(int i=0;i<count;i++) {
366             int word = dis.readInt();
367             if((word&3)==0 && word >= textStart && word < textEnd) {
368                 if(jumps.put(new Integer(word),Boolean.TRUE) == null) {
369                     //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
370                     n++;
371                 }
372             }
373         }
374         dis.close();
375         if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
376     }
377     
378     // Helper functions for pretty output
379     final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
380     final static String toHex8(int n) {
381         String s = Long.toString(n & 0xffffffffL, 16);
382         StringBuffer sb = new StringBuffer("0x");
383         for(int i=8-s.length();i>0;i--) sb.append('0');
384         sb.append(s);
385         return sb.toString();
386     }
387
388     final static String toOctal3(int n) {
389         char[] buf = new char[3];
390         for(int i=2;i>=0;i--) {
391             buf[i] = (char) ('0' + (n & 7));
392             n >>= 3;
393         }
394         return new String(buf);
395     }
396
397     // Option parsing
398     private class Option {
399         private java.lang.reflect.Field field;
400         public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
401         public void set(Object val) {
402             if(field == null) return;
403             try {
404                 /*field.setAccessible(true); NOT in JDK 1.1 */
405                 field.set(Compiler.this,val);
406             } catch(IllegalAccessException e) {
407                 System.err.println(e);
408             }
409         }
410         public Object get() {
411             if(field == null) return null;
412             try {
413                 /*field.setAccessible(true); NOT in JDK 1.1 */
414                 return field.get(Compiler.this);
415             } catch(IllegalAccessException e) {
416                 System.err.println(e); return null;
417             }
418         }
419         public Class getType() { return field == null ? null : field.getType(); }
420     }
421     
422     private static String[] options = {
423         "fastMem",          "Enable fast memory access - RuntimeExceptions will be thrown on faults",
424         "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
425         "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
426         "pruneCases",       "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
427         "assumeTailCalls",  "Assume the JIT optimizes tail calls",
428         "optimizedMemcpy",  "Use an optimized java version of memcpy where possible",
429         "debugCompiler",    "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
430         "printStats",       "Output some useful statistics about the compilation",
431         "runtimeStats",     "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
432         "supportCall",      "Keep a stripped down version of the symbol table in the generated code to support the call() method",
433         "runtimeClass",     "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
434         "hashClass",        "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
435         "unixRuntime",      "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
436         "pageSize",         "The page size (must be a power of two)",
437         "totalPages",       "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
438         "onePage",          "One page hack (FIXME: document this better)",
439         "lessConstants",    "Use less constants at the cost of speed (FIXME: document this better)",
440         "singleFloat",      "Support single precision (32-bit) FP ops only"
441     };
442         
443     private Option getOption(String name) {
444         name = name.toLowerCase();
445         try {
446             for(int i=0;i<options.length;i+=2)
447                 if(options[i].toLowerCase().equals(name))
448                     return new Option(options[i]);
449             return null;
450         } catch(NoSuchFieldException e) {
451             return null;
452         }
453     }
454     
455     public void parseOptions(String opts) {
456         if(opts == null || opts.length() == 0) return;
457         StringTokenizer st = new StringTokenizer(opts,",");
458         while(st.hasMoreElements()) {
459             String tok = st.nextToken();
460             String key;
461             String val;
462             if(tok.indexOf("=") != -1) {
463                 key = tok.substring(0,tok.indexOf("="));
464                 val = tok.substring(tok.indexOf("=")+1);
465             } else if(tok.startsWith("no")) {
466                 key = tok.substring(2);
467                 val = "false";
468             } else {
469                 key = tok;
470                 val = "true";
471             }
472             Option opt = getOption(key);
473             if(opt == null) {
474                 System.err.println("WARNING: No such option: " + key);
475                 continue;
476             }
477             
478             if(opt.getType() == String.class)
479                 opt.set(val);
480             else if(opt.getType() == Integer.TYPE)
481                 try {
482                     opt.set(parseInt(val));
483                 } catch(NumberFormatException e) {
484                     System.err.println("WARNING: " + val + " is not an integer");
485                 }
486             else if(opt.getType() == Boolean.TYPE)
487                 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
488             else
489                 throw new Error("Unknown type: " + opt.getType());
490         }
491     }
492         
493     private static Integer parseInt(String s) {
494         int mult = 1;
495         s = s.toLowerCase();
496         if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
497         else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
498         int n;
499         if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
500         else n = Integer.parseInt(s);
501         return new Integer(n*mult);
502     }
503     
504     private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
505         StringTokenizer st = new StringTokenizer(s," ");
506         StringBuffer sb = new StringBuffer();
507         for(int i=0;i<firstindent;i++)
508             sb.append(' ');
509         int sofar = 0;
510         while(st.hasMoreTokens()) {
511             String tok = st.nextToken();
512             if(tok.length() + sofar + 1 > width && sofar > 0) {
513                 sb.append('\n');
514                 for(int i=0;i<indent;i++) sb.append(' ');
515                 sofar = 0;
516             } else if(sofar > 0) {
517                 sb.append(' ');
518                 sofar++;
519             }
520             sb.append(tok);
521             sofar += tok.length();
522         }
523         sb.append('\n');
524         return sb.toString();
525     }
526     
527     // This ugliness is to work around a gcj static linking bug (Bug 12908)
528     // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
529     static String dateTime() {
530         try {
531             return new Date().toString();
532         } catch(RuntimeException e) {
533             return "<unknown>";
534         }
535     }
536 }
537