68d2dee97da005d1c3f5b61380b36c55e4ec6042
[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         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     /** The address of the memcpy function in the binary (used for optimizedMemcpy) */
88     protected int memcpy;
89
90     /** The address of the memset function in the binary (used for optimizedMemcpy) */
91     protected int memset;
92     
93     /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
94     protected Set jumpableAddresses;
95     
96     /** Some important symbols */
97     ELF.Symbol userInfo, gp;
98     
99     private static void usage() {
100         System.err.println("Usage: java Compiler [-outfile output.java] [-o options] [-dumpoptions] <classname> <binary.mips>");
101         System.err.println("-o takes mount(8) like options and can be specified multiple times");
102         System.err.println("Available options:");
103         for(int i=0;i<options.length;i+=2)
104             System.err.print(options[i] + ": " + wrapAndIndent(options[i+1],18-2-options[i].length(),18,62));
105         System.exit(1);
106     }
107     
108     public static void main(String[] args) throws IOException {
109         String outfile = null;
110         String o = null;
111         String className = null;
112         String mipsBinaryFileName = null;
113         String outformat = null;
114         boolean dumpOptions = false;
115         int arg = 0;
116         while(args.length-arg > 0) {
117             if(args[arg].equals("-outfile")) {
118                 arg++;
119                 if(arg==args.length) usage();
120                 outfile = args[arg];
121             } else if(args[arg].equals("-outformat")) {
122                 arg++;
123                 if(arg==args.length) usage();
124                 outformat = args[arg];
125             } else if(args[arg].equals("-o")) {
126                 arg++;
127                 if(arg==args.length) usage();
128                 if(o==null || o.length() == 0)
129                     o = args[arg];
130                 else if(args[arg].length() != 0)
131                     o += "," + args[arg];
132             } else if(args[arg].equals("-dumpoptions")) {
133                 dumpOptions = true;
134             } else if(className == null) {
135                 className = args[arg];
136             } else if(mipsBinaryFileName == null) {
137                 mipsBinaryFileName = args[arg];
138             } else {
139                 usage();
140             }
141             arg++;
142         }
143         if(className == null || mipsBinaryFileName == null) usage();
144         
145         Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
146         
147         Writer w = null;
148         OutputStream os = null;
149         Compiler comp = null;
150         if(outformat == null || outformat.equals("class")) {
151             if(outfile == null) {
152                 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
153                 System.exit(1);
154             }
155             os = new FileOutputStream(outfile);
156             comp = new ClassFileCompiler(mipsBinary,className,os);
157         } else if(outformat.equals("javasource") || outformat .equals("java")) {
158             w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
159             comp = new JavaSourceCompiler(mipsBinary,className,w);
160         } else {
161             System.err.println("Unknown output format: " + outformat);
162             System.exit(1);
163         }
164         
165         comp.parseOptions(o);
166         comp.setSource(mipsBinaryFileName);
167         
168         if(dumpOptions) {
169             System.err.println("== Options ==");
170             for(int i=0;i<options.length;i+=2)
171                 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
172             System.err.println("== End Options ==");
173         }
174             
175         try {
176             comp.go();
177         } catch(Exn e) {
178             System.err.println("Compiler Error: " + e.getMessage());
179             System.exit(1);
180         } finally {
181             if(w != null) w.close();
182             if(os != null) os.close();
183         }
184     }
185         
186     public Compiler(Seekable binary, String fullClassName) throws IOException {
187         this.fullClassName = fullClassName;
188         elf = new ELF(binary);
189         
190         if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
191         if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
192         if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
193     }
194
195     protected abstract void _go() throws Exn, IOException;
196     
197     private boolean used;
198     public void go() throws Exn, IOException {
199         if(used) throw new RuntimeException("Compiler instances are good for one shot only");
200         used = true;
201         
202         if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
203         if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
204         if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
205         if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
206         if(onePage) totalPages = 1;
207
208         maxInsnPerMethodInit();
209         pageSizeInit();
210         
211         // Get a copy of the symbol table in the elf binary
212         ELF.Symtab symtab = elf.getSymtab();
213         if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
214         ELF.Symbol sym;
215         
216         // Check for some functions we can override
217         sym = symtab.getGlobalSymbol("memcpy");
218         memcpy = sym == null ? -1 : sym.addr;
219         
220         sym = symtab.getGlobalSymbol("memset");
221         memset = sym == null ? -1 : sym.addr;
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 HashSet();
230             
231             jumpableAddresses.add(new Integer(elf.header.entry));
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].addr != 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, Set 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.add(new Integer(s.addr))) {
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, Set 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.add(new Integer(pc+8))) n++; // return address
301                             break;
302                         case 12: // SYSCALL
303                             if(jumps.add(new Integer(pc+4))) n++; 
304                             break;
305                     }
306                     break;
307                 case 1:
308                     switch(rt) {
309                         case 16: // BLTZAL
310                         case 17: // BGTZAL
311                             if(jumps.add(new Integer(pc+8))) n++; // return address
312                             // fall through
313                         case 0: // BLTZ
314                         case 1: // BGEZ
315                             if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
316                             break;
317                     }
318                     break;
319                 case 3: // JAL
320                     if(jumps.add(new Integer(pc+8))) n++; // return address
321                     // fall through
322                 case 2: // J
323                     if(jumps.add(new Integer((pc&0xf0000000)|(jumpTarget << 2)))) n++;
324                     break;
325                 case 4: // BEQ
326                 case 5: // BNE
327                 case 6: // BLEZ
328                 case 7: // BGTZ
329                     if(jumps.add(new Integer(pc+branchTarget*4+4))) 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.add(new Integer(t))) {
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.add(new Integer(pc+branchTarget*4+4))) 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, Set 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.add(new Integer(word))) {
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     protected final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
382     protected 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     protected 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);
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);
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     };
443         
444     private Option getOption(String name) {
445         name = name.toLowerCase();
446         try {
447             for(int i=0;i<options.length;i+=2)
448                 if(options[i].toLowerCase().equals(name))
449                     return new Option(options[i]);
450             return null;
451         } catch(NoSuchFieldException e) {
452             return null;
453         }
454     }
455     
456     public void parseOptions(String opts) {
457         if(opts == null || opts.length() == 0) return;
458         StringTokenizer st = new StringTokenizer(opts,",");
459         while(st.hasMoreElements()) {
460             String tok = st.nextToken();
461             String key;
462             String val;
463             if(tok.indexOf("=") != -1) {
464                 key = tok.substring(0,tok.indexOf("="));
465                 val = tok.substring(tok.indexOf("=")+1);
466             } else if(tok.startsWith("no")) {
467                 key = tok.substring(2);
468                 val = "false";
469             } else {
470                 key = tok;
471                 val = "true";
472             }
473             Option opt = getOption(key);
474             if(opt == null) {
475                 System.err.println("WARNING: No such option: " + key);
476                 continue;
477             }
478             
479             if(opt.getType() == String.class)
480                 opt.set(val);
481             else if(opt.getType() == Integer.TYPE)
482                 try {
483                     opt.set(parseInt(val));
484                 } catch(NumberFormatException e) {
485                     System.err.println("WARNING: " + val + " is not an integer");
486                 }
487             else if(opt.getType() == Boolean.TYPE)
488                 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
489             else
490                 throw new Error("Unknown type: " + opt.getType());
491         }
492     }
493         
494     private static Integer parseInt(String s) {
495         int mult = 1;
496         s = s.toLowerCase();
497         if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
498         else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
499         int n;
500         if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
501         else n = Integer.parseInt(s);
502         return new Integer(n*mult);
503     }
504     
505     private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
506         StringTokenizer st = new StringTokenizer(s," ");
507         StringBuffer sb = new StringBuffer();
508         for(int i=0;i<firstindent;i++)
509             sb.append(' ');
510         int sofar = 0;
511         while(st.hasMoreTokens()) {
512             String tok = st.nextToken();
513             if(tok.length() + sofar + 1 > width && sofar > 0) {
514                 sb.append('\n');
515                 for(int i=0;i<indent;i++) sb.append(' ');
516                 sofar = 0;
517             } else if(sofar > 0) {
518                 sb.append(' ');
519                 sofar++;
520             }
521             sb.append(tok);
522             sofar += tok.length();
523         }
524         sb.append('\n');
525         return sb.toString();
526     }
527     
528     // This ugliness is to work around a gcj static linking bug (Bug 12908)
529     // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
530     protected static String dateTime() {
531         try {
532             return new Date().toString();
533         } catch(RuntimeException e) {
534             return "<unknown>";
535         }
536     }
537 }
538