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