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