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