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