1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
3 package org.ibex.nestedvm;
8 import org.ibex.nestedvm.util.*;
10 public abstract class Compiler implements Registers {
11 /** The ELF binary being read */
14 /** The name of the class beging generated */
15 final String fullClassName;
17 /** The name of the binary this class is begin generated from */
18 String source = "unknown.mips.binary";
19 public void setSource(String source) { this.source = source; }
21 /** Thrown when the compilation fails for some reason */
22 static class Exn extends Exception { public Exn(String s) { super(s); } }
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 boolean fastMem = true;
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
33 int maxInsnPerMethod = 128;
36 int maxBytesPerMethod;
39 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++;
46 // True to try to determine which case statement are needed and only include them
47 boolean pruneCases = true;
49 boolean assumeTailCalls = true;
51 // True to insert some code in the output to help diagnore compiler problems
52 boolean debugCompiler = false;
54 // True to print various statistics about the compilation
55 boolean printStats = false;
57 // True to generate runtime statistics that slow execution down significantly
58 boolean runtimeStats = false;
60 boolean supportCall = true;
62 boolean nullPointerCheck = false;
64 String runtimeClass = "org.ibex.nestedvm.Runtime";
66 String hashClass = "java.util.Hashtable";
70 boolean lessConstants;
75 int totalPages = 65536;
79 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++;
85 /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
86 Hashtable jumpableAddresses;
88 /** Some important symbols */
89 ELF.Symbol userInfo, gp;
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));
100 public static void main(String[] args) throws IOException {
101 String outfile = null;
102 String outdir = null;
104 String className = null;
105 String mipsBinaryFileName = null;
106 String outformat = null;
107 boolean dumpOptions = false;
109 while(args.length-arg > 0) {
110 if(args[arg].equals("-outfile")) {
112 if(arg==args.length) usage();
114 } else if(args[arg].equals("-d")) {
116 if(arg==args.length) usage();
118 } else if(args[arg].equals("-outformat")) {
120 if(arg==args.length) usage();
121 outformat = args[arg];
122 } else if(args[arg].equals("-o")) {
124 if(arg==args.length) usage();
125 if(o==null || o.length() == 0)
127 else if(args[arg].length() != 0)
128 o += "," + args[arg];
129 } else if(args[arg].equals("-dumpoptions")) {
131 } else if(className == null) {
132 className = args[arg];
133 } else if(mipsBinaryFileName == null) {
134 mipsBinaryFileName = args[arg];
140 if(className == null || mipsBinaryFileName == null) usage();
142 Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
145 OutputStream os = null;
146 Compiler comp = null;
147 if(outformat == null || outformat.equals("class")) {
148 if(outfile != null) {
149 os = new FileOutputStream(outfile);
150 comp = new ClassFileCompiler(mipsBinary,className,os);
151 } else if(outdir != null) {
152 File f = new File(outdir);
153 if(!f.isDirectory()) {
154 System.err.println(outdir + " doesn't exist or is not a directory");
157 comp = new ClassFileCompiler(mipsBinary,className,f);
159 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
162 } else if(outformat.equals("javasource") || outformat .equals("java")) {
163 w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
164 comp = new JavaSourceCompiler(mipsBinary,className,w);
166 System.err.println("Unknown output format: " + outformat);
170 comp.parseOptions(o);
171 comp.setSource(mipsBinaryFileName);
174 System.err.println("== Options ==");
175 for(int i=0;i<options.length;i+=2)
176 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
177 System.err.println("== End Options ==");
183 System.err.println("Compiler Error: " + e.getMessage());
186 if(w != null) w.close();
187 if(os != null) os.close();
191 public Compiler(Seekable binary, String fullClassName) throws IOException {
192 this.fullClassName = fullClassName;
193 elf = new ELF(binary);
195 if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
196 if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
197 if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
200 abstract void _go() throws Exn, IOException;
202 private boolean used;
203 public void go() throws Exn, IOException {
204 if(used) throw new RuntimeException("Compiler instances are good for one shot only");
207 if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
208 if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
209 if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
210 if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
211 if(onePage) totalPages = 1;
213 maxInsnPerMethodInit();
216 // Get a copy of the symbol table in the elf binary
217 ELF.Symtab symtab = elf.getSymtab();
218 if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
221 userInfo = symtab.getGlobalSymbol("user_info");
222 gp = symtab.getGlobalSymbol("_gp");
223 if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");
226 // Find all possible branches
227 jumpableAddresses = new Hashtable();
229 jumpableAddresses.put(new Integer(elf.header.entry),Boolean.TRUE);
231 ELF.SHeader text = elf.sectionWithName(".text");
232 if(text == null) throw new Exn("No .text segment");
234 findBranchesInSymtab(symtab,jumpableAddresses);
236 for(int i=0;i<elf.sheaders.length;i++) {
237 ELF.SHeader sheader = elf.sheaders[i];
238 String name = sheader.name;
239 // if this section doesn't get loaded into our address space don't worry about it
240 if(sheader.addr == 0x0) continue;
241 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
242 findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
245 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
248 if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
250 for(int i=0;i<elf.sheaders.length;i++) {
251 String name = elf.sheaders[i].name;
252 if(elf.sheaders[i].addr != 0 && !(
253 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
254 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
255 throw new Exn("Unknown section: " + name);
260 private void findBranchesInSymtab(ELF.Symtab symtab, Hashtable jumps) {
261 ELF.Symbol[] symbols = symtab.symbols;
263 for(int i=0;i<symbols.length;i++) {
264 ELF.Symbol s = symbols[i];
265 if(s.type == ELF.Symbol.STT_FUNC) {
266 if(jumps.put(new Integer(s.addr),Boolean.TRUE) == null) {
267 //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
272 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
275 private void findBranchesInText(int base, DataInputStream dis, int size, Hashtable jumps) throws IOException {
279 int[] lui_val = new int[32];
280 int[] lui_pc = new int[32];
281 //Interpreter inter = new Interpreter(source);
283 for(int i=0;i<count;i++,pc+=4) {
284 int insn = dis.readInt();
285 int op = (insn >>> 26) & 0xff;
286 int rs = (insn >>> 21) & 0x1f;
287 int rt = (insn >>> 16) & 0x1f;
288 int signedImmediate = (insn << 16) >> 16;
289 int unsignedImmediate = insn & 0xffff;
290 int branchTarget = signedImmediate;
291 int jumpTarget = (insn & 0x03ffffff);
292 int subcode = insn & 0x3f;
298 if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
301 if(jumps.put(new Integer(pc+4),Boolean.TRUE) == null) n++;
309 if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
313 if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
318 if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
321 if(jumps.put(new Integer((pc&0xf0000000)|(jumpTarget << 2)),Boolean.TRUE) == null) n++;
327 if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
330 if(pc - lui_pc[rs] <= 4*32) {
331 int t = (lui_val[rs]<<16)+signedImmediate;
332 if((t&3)==0 && t >= base && t < base+size) {
333 if(jumps.put(new Integer(t),Boolean.TRUE) == null) {
334 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
338 // we just blew it away
339 if(rt == rs) lui_pc[rs] = 0;
344 lui_val[rt] = unsignedImmediate;
349 case 17: // FPU Instructions
351 case 8: // BC1F, BC1T
352 if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
359 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
362 private void findBranchesInData(DataInputStream dis, int size, Hashtable jumps, int textStart, int textEnd) throws IOException {
365 for(int i=0;i<count;i++) {
366 int word = dis.readInt();
367 if((word&3)==0 && word >= textStart && word < textEnd) {
368 if(jumps.put(new Integer(word),Boolean.TRUE) == null) {
369 //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
375 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
378 // Helper functions for pretty output
379 final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
380 final static String toHex8(int n) {
381 String s = Long.toString(n & 0xffffffffL, 16);
382 StringBuffer sb = new StringBuffer("0x");
383 for(int i=8-s.length();i>0;i--) sb.append('0');
385 return sb.toString();
388 final static String toOctal3(int n) {
389 char[] buf = new char[3];
390 for(int i=2;i>=0;i--) {
391 buf[i] = (char) ('0' + (n & 7));
394 return new String(buf);
398 private class Option {
399 private java.lang.reflect.Field field;
400 public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
401 public void set(Object val) {
402 if(field == null) return;
404 /*field.setAccessible(true); NOT in JDK 1.1 */
405 field.set(Compiler.this,val);
406 } catch(IllegalAccessException e) {
407 System.err.println(e);
410 public Object get() {
411 if(field == null) return null;
413 /*field.setAccessible(true); NOT in JDK 1.1 */
414 return field.get(Compiler.this);
415 } catch(IllegalAccessException e) {
416 System.err.println(e); return null;
419 public Class getType() { return field == null ? null : field.getType(); }
422 private static String[] options = {
423 "fastMem", "Enable fast memory access - RuntimeExceptions will be thrown on faults",
424 "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
425 "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
426 "pruneCases", "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
427 "assumeTailCalls", "Assume the JIT optimizes tail calls",
428 "optimizedMemcpy", "Use an optimized java version of memcpy where possible",
429 "debugCompiler", "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
430 "printStats", "Output some useful statistics about the compilation",
431 "runtimeStats", "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
432 "supportCall", "Keep a stripped down version of the symbol table in the generated code to support the call() method",
433 "runtimeClass", "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
434 "hashClass", "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
435 "unixRuntime", "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
436 "pageSize", "The page size (must be a power of two)",
437 "totalPages", "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
438 "onePage", "One page hack (FIXME: document this better)",
439 "lessConstants", "Use less constants at the cost of speed (FIXME: document this better)",
440 "singleFloat", "Support single precision (32-bit) FP ops only"
443 private Option getOption(String name) {
444 name = name.toLowerCase();
446 for(int i=0;i<options.length;i+=2)
447 if(options[i].toLowerCase().equals(name))
448 return new Option(options[i]);
450 } catch(NoSuchFieldException e) {
455 public void parseOptions(String opts) {
456 if(opts == null || opts.length() == 0) return;
457 StringTokenizer st = new StringTokenizer(opts,",");
458 while(st.hasMoreElements()) {
459 String tok = st.nextToken();
462 if(tok.indexOf("=") != -1) {
463 key = tok.substring(0,tok.indexOf("="));
464 val = tok.substring(tok.indexOf("=")+1);
465 } else if(tok.startsWith("no")) {
466 key = tok.substring(2);
472 Option opt = getOption(key);
474 System.err.println("WARNING: No such option: " + key);
478 if(opt.getType() == String.class)
480 else if(opt.getType() == Integer.TYPE)
482 opt.set(parseInt(val));
483 } catch(NumberFormatException e) {
484 System.err.println("WARNING: " + val + " is not an integer");
486 else if(opt.getType() == Boolean.TYPE)
487 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
489 throw new Error("Unknown type: " + opt.getType());
493 private static Integer parseInt(String s) {
496 if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
497 else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
499 if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
500 else n = Integer.parseInt(s);
501 return new Integer(n*mult);
504 private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
505 StringTokenizer st = new StringTokenizer(s," ");
506 StringBuffer sb = new StringBuffer();
507 for(int i=0;i<firstindent;i++)
510 while(st.hasMoreTokens()) {
511 String tok = st.nextToken();
512 if(tok.length() + sofar + 1 > width && sofar > 0) {
514 for(int i=0;i<indent;i++) sb.append(' ');
516 } else if(sofar > 0) {
521 sofar += tok.length();
524 return sb.toString();
527 // This ugliness is to work around a gcj static linking bug (Bug 12908)
528 // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
529 static String dateTime() {
531 return new Date().toString();
532 } catch(RuntimeException e) {