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