1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
3 package org.ibex.nestedvm;
8 import org.ibex.nestedvm.util.*;
10 // FEATURE: -d option for classfilecompiler (just like javac's -d)
12 public abstract class Compiler implements Registers {
13 /** The ELF binary being read */
16 /** The name of the class beging generated */
17 protected final String fullClassName;
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; }
23 /** Thrown when the compilation fails for some reason */
24 protected static class Exn extends Exception { public Exn(String s) { super(s); } }
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;
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
35 protected int maxInsnPerMethod = 128;
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++;
48 // True to try to determine which case statement are needed and only include them
49 protected boolean pruneCases = true;
51 protected boolean assumeTailCalls = true;
53 protected boolean optimizedMemcpy = true;
55 // True to insert some code in the output to help diagnore compiler problems
56 protected boolean debugCompiler = false;
58 // True to print various statistics about the compilation
59 protected boolean printStats = false;
61 // True to generate runtime statistics that slow execution down significantly
62 protected boolean runtimeStats = false;
64 protected boolean supportCall = true;
66 protected boolean nullPointerCheck = false;
68 protected String runtimeClass = "org.ibex.nestedvm.Runtime";
70 protected String hashClass = "java.util.Hashtable";
72 protected boolean unixRuntime;
74 protected boolean lessConstants = false;
76 protected int pageSize = 4096;
77 protected int totalPages = 65536;
78 protected int pageShift;
79 protected boolean onePage;
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++;
87 /** The address of the memcpy function in the binary (used for optimizedMemcpy) */
90 /** The address of the memset function in the binary (used for optimizedMemcpy) */
93 /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
94 protected Set jumpableAddresses;
96 /** Some important symbols */
97 ELF.Symbol userInfo, gp;
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));
108 public static void main(String[] args) throws IOException {
109 String outfile = null;
111 String className = null;
112 String mipsBinaryFileName = null;
113 String outformat = null;
114 boolean dumpOptions = false;
116 while(args.length-arg > 0) {
117 if(args[arg].equals("-outfile")) {
119 if(arg==args.length) usage();
121 } else if(args[arg].equals("-outformat")) {
123 if(arg==args.length) usage();
124 outformat = args[arg];
125 } else if(args[arg].equals("-o")) {
127 if(arg==args.length) usage();
128 if(o==null || o.length() == 0)
130 else if(args[arg].length() != 0)
131 o += "," + args[arg];
132 } else if(args[arg].equals("-dumpoptions")) {
134 } else if(className == null) {
135 className = args[arg];
136 } else if(mipsBinaryFileName == null) {
137 mipsBinaryFileName = args[arg];
143 if(className == null || mipsBinaryFileName == null) usage();
145 Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
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");
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);
161 System.err.println("Unknown output format: " + outformat);
165 comp.parseOptions(o);
166 comp.setSource(mipsBinaryFileName);
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 ==");
178 System.err.println("Compiler Error: " + e.getMessage());
181 if(w != null) w.close();
182 if(os != null) os.close();
186 public Compiler(Seekable binary, String fullClassName) throws IOException {
187 this.fullClassName = fullClassName;
188 elf = new ELF(binary);
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");
195 protected abstract void _go() throws Exn, IOException;
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");
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;
208 maxInsnPerMethodInit();
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?)");
216 // Check for some functions we can override
217 sym = symtab.getGlobalSymbol("memcpy");
218 memcpy = sym == null ? -1 : sym.addr;
220 sym = symtab.getGlobalSymbol("memset");
221 memset = sym == null ? -1 : sym.addr;
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?)");
228 // Find all possible branches
229 jumpableAddresses = new HashSet();
231 jumpableAddresses.add(new Integer(elf.header.entry));
233 ELF.SHeader text = elf.sectionWithName(".text");
234 if(text == null) throw new Exn("No .text segment");
236 findBranchesInSymtab(symtab,jumpableAddresses);
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);
247 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
250 if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
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);
262 private void findBranchesInSymtab(ELF.Symtab symtab, Set jumps) {
263 ELF.Symbol[] symbols = symtab.symbols;
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));
274 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
277 private void findBranchesInText(int base, DataInputStream dis, int size, Set jumps) throws IOException {
281 int[] lui_val = new int[32];
282 int[] lui_pc = new int[32];
283 //Interpreter inter = new Interpreter(source);
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;
300 if(jumps.add(new Integer(pc+8))) n++; // return address
303 if(jumps.add(new Integer(pc+4))) n++;
311 if(jumps.add(new Integer(pc+8))) n++; // return address
315 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
320 if(jumps.add(new Integer(pc+8))) n++; // return address
323 if(jumps.add(new Integer((pc&0xf0000000)|(jumpTarget << 2)))) n++;
329 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
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) + ")");
340 // we just blew it away
341 if(rt == rs) lui_pc[rs] = 0;
346 lui_val[rt] = unsignedImmediate;
351 case 17: // FPU Instructions
353 case 8: // BC1F, BC1T
354 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
361 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
364 private void findBranchesInData(DataInputStream dis, int size, Set jumps, int textStart, int textEnd) throws IOException {
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)");
377 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
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');
387 return sb.toString();
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));
396 return new String(buf);
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;
406 field.setAccessible(true);
407 field.set(Compiler.this,val);
408 } catch(IllegalAccessException e) {
409 System.err.println(e);
412 public Object get() {
413 if(field == null) return null;
415 field.setAccessible(true);
416 return field.get(Compiler.this);
417 } catch(IllegalAccessException e) {
418 System.err.println(e); return null;
421 public Class getType() { return field == null ? null : field.getType(); }
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)"
444 private Option getOption(String name) {
445 name = name.toLowerCase();
447 for(int i=0;i<options.length;i+=2)
448 if(options[i].toLowerCase().equals(name))
449 return new Option(options[i]);
451 } catch(NoSuchFieldException e) {
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();
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);
473 Option opt = getOption(key);
475 System.err.println("WARNING: No such option: " + key);
479 if(opt.getType() == String.class)
481 else if(opt.getType() == Integer.TYPE)
483 opt.set(parseInt(val));
484 } catch(NumberFormatException e) {
485 System.err.println("WARNING: " + val + " is not an integer");
487 else if(opt.getType() == Boolean.TYPE)
488 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
490 throw new Error("Unknown type: " + opt.getType());
494 private static Integer parseInt(String s) {
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; }
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);
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++)
511 while(st.hasMoreTokens()) {
512 String tok = st.nextToken();
513 if(tok.length() + sofar + 1 > width && sofar > 0) {
515 for(int i=0;i<indent;i++) sb.append(' ');
517 } else if(sofar > 0) {
522 sofar += tok.length();
525 return sb.toString();
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() {
532 return new Date().toString();
533 } catch(RuntimeException e) {