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 {
83 Runtime.checkPageSize(pageSize,totalPages);
84 } catch(IllegalArgumentException e) {
85 throw new Exn(e.getMessage());
87 while(pageSize>>>pageShift != 1) pageShift++;
90 /** The address of the memcpy function in the binary (used for optimizedMemcpy) */
93 /** The address of the memset function in the binary (used for optimizedMemcpy) */
96 /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
97 protected Set jumpableAddresses;
99 /** Some important symbols */
100 ELF.Symbol userInfo, gp;
102 private static void usage() {
103 System.err.println("Usage: java Compiler [-outfile output.java] [-o options] [-dumpoptions] <classname> <binary.mips>");
104 System.err.println("-o takes mount(8) like options and can be specified multiple times");
105 System.err.println("Available options:");
106 for(int i=0;i<options.length;i+=2)
107 System.err.print(options[i] + ": " + wrapAndIndent(options[i+1],18-2-options[i].length(),18,62));
111 public static void main(String[] args) throws IOException {
112 String outfile = null;
114 String className = null;
115 String mipsBinaryFileName = null;
116 String outformat = null;
117 boolean dumpOptions = false;
119 while(args.length-arg > 0) {
120 if(args[arg].equals("-outfile")) {
122 if(arg==args.length) usage();
124 } else if(args[arg].equals("-outformat")) {
126 if(arg==args.length) usage();
127 outformat = args[arg];
128 } else if(args[arg].equals("-o")) {
130 if(arg==args.length) usage();
131 if(o==null || o.length() == 0)
133 else if(args[arg].length() != 0)
134 o += "," + args[arg];
135 } else if(args[arg].equals("-dumpoptions")) {
137 } else if(className == null) {
138 className = args[arg];
139 } else if(mipsBinaryFileName == null) {
140 mipsBinaryFileName = args[arg];
146 if(className == null || mipsBinaryFileName == null) usage();
148 Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
151 OutputStream os = null;
152 Compiler comp = null;
153 if(outformat == null || outformat.equals("class")) {
154 if(outfile == null) {
155 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
158 os = new FileOutputStream(outfile);
159 comp = new ClassFileCompiler(mipsBinary,className,os);
160 } else if(outformat.equals("javasource") || outformat .equals("java")) {
161 w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
162 comp = new JavaSourceCompiler(mipsBinary,className,w);
164 System.err.println("Unknown output format: " + outformat);
168 comp.parseOptions(o);
169 comp.setSource(mipsBinaryFileName);
172 System.err.println("== Options ==");
173 for(int i=0;i<options.length;i+=2)
174 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
175 System.err.println("== End Options ==");
181 System.err.println("Compiler Error: " + e.getMessage());
184 if(w != null) w.close();
185 if(os != null) os.close();
189 public Compiler(Seekable binary, String fullClassName) throws IOException {
190 this.fullClassName = fullClassName;
191 elf = new ELF(binary);
193 if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
194 if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
195 if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
198 protected abstract void _go() throws Exn, IOException;
200 private boolean used;
201 public void go() throws Exn, IOException {
202 if(used) throw new RuntimeException("Compiler instances are good for one shot only");
205 if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
206 if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
207 if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
208 if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
209 if(onePage) totalPages = 1;
211 maxInsnPerMethodInit();
214 // Get a copy of the symbol table in the elf binary
215 ELF.Symtab symtab = elf.getSymtab();
216 if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
219 // Check for some functions we can override
220 sym = symtab.getGlobalSymbol("memcpy");
221 memcpy = sym == null ? -1 : sym.addr;
223 sym = symtab.getGlobalSymbol("memset");
224 memset = sym == null ? -1 : sym.addr;
226 userInfo = symtab.getGlobalSymbol("user_info");
227 gp = symtab.getGlobalSymbol("_gp");
228 if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");
231 // Find all possible branches
232 jumpableAddresses = new HashSet();
234 jumpableAddresses.add(new Integer(elf.header.entry));
236 ELF.SHeader text = elf.sectionWithName(".text");
237 if(text == null) throw new Exn("No .text segment");
239 findBranchesInSymtab(symtab,jumpableAddresses);
241 for(int i=0;i<elf.sheaders.length;i++) {
242 ELF.SHeader sheader = elf.sheaders[i];
243 String name = sheader.name;
244 // if this section doesn't get loaded into our address space don't worry about it
245 if(sheader.addr == 0x0) continue;
246 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
247 findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
250 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
253 if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
255 for(int i=0;i<elf.sheaders.length;i++) {
256 String name = elf.sheaders[i].name;
257 if(elf.sheaders[i].addr != 0 && !(
258 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
259 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
260 throw new Exn("Unknown section: " + name);
265 private void findBranchesInSymtab(ELF.Symtab symtab, Set jumps) {
266 ELF.Symbol[] symbols = symtab.symbols;
268 for(int i=0;i<symbols.length;i++) {
269 ELF.Symbol s = symbols[i];
270 if(s.type == ELF.Symbol.STT_FUNC) {
271 if(jumps.add(new Integer(s.addr))) {
272 //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
277 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
280 private void findBranchesInText(int base, DataInputStream dis, int size, Set jumps) throws IOException {
284 int[] lui_val = new int[32];
285 int[] lui_pc = new int[32];
286 //Interpreter inter = new Interpreter(source);
288 for(int i=0;i<count;i++,pc+=4) {
289 int insn = dis.readInt();
290 int op = (insn >>> 26) & 0xff;
291 int rs = (insn >>> 21) & 0x1f;
292 int rt = (insn >>> 16) & 0x1f;
293 int signedImmediate = (insn << 16) >> 16;
294 int unsignedImmediate = insn & 0xffff;
295 int branchTarget = signedImmediate;
296 int jumpTarget = (insn & 0x03ffffff);
297 int subcode = insn & 0x3f;
303 if(jumps.add(new Integer(pc+8))) n++; // return address
306 if(jumps.add(new Integer(pc+4))) n++;
314 if(jumps.add(new Integer(pc+8))) n++; // return address
318 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
323 if(jumps.add(new Integer(pc+8))) n++; // return address
326 if(jumps.add(new Integer((pc&0xf0000000)|(jumpTarget << 2)))) n++;
332 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
335 if(pc - lui_pc[rs] <= 4*32) {
336 int t = (lui_val[rs]<<16)+signedImmediate;
337 if((t&3)==0 && t >= base && t < base+size) {
338 if(jumps.add(new Integer(t))) {
339 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
343 // we just blew it away
344 if(rt == rs) lui_pc[rs] = 0;
349 lui_val[rt] = unsignedImmediate;
354 case 17: // FPU Instructions
356 case 8: // BC1F, BC1T
357 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
364 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
367 private void findBranchesInData(DataInputStream dis, int size, Set jumps, int textStart, int textEnd) throws IOException {
370 for(int i=0;i<count;i++) {
371 int word = dis.readInt();
372 if((word&3)==0 && word >= textStart && word < textEnd) {
373 if(jumps.add(new Integer(word))) {
374 //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
380 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
383 // Helper functions for pretty output
384 protected final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
385 protected final static String toHex8(int n) {
386 String s = Long.toString(n & 0xffffffffL, 16);
387 StringBuffer sb = new StringBuffer("0x");
388 for(int i=8-s.length();i>0;i--) sb.append('0');
390 return sb.toString();
393 protected final static String toOctal3(int n) {
394 char[] buf = new char[3];
395 for(int i=2;i>=0;i--) {
396 buf[i] = (char) ('0' + (n & 7));
399 return new String(buf);
403 private class Option {
404 private java.lang.reflect.Field field;
405 public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
406 public void set(Object val) {
407 if(field == null) return;
409 field.setAccessible(true);
410 field.set(Compiler.this,val);
411 } catch(IllegalAccessException e) {
412 System.err.println(e);
415 public Object get() {
416 if(field == null) return null;
418 field.setAccessible(true);
419 return field.get(Compiler.this);
420 } catch(IllegalAccessException e) {
421 System.err.println(e); return null;
424 public Class getType() { return field == null ? null : field.getType(); }
427 private static String[] options = {
428 "fastMem", "Enable fast memory access - RuntimeExceptions will be thrown on faults",
429 "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
430 "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
431 "pruneCases", "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
432 "assumeTailCalls", "Assume the JIT optimizes tail calls",
433 "optimizedMemcpy", "Use an optimized java version of memcpy where possible",
434 "debugCompiler", "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
435 "printStats", "Output some useful statistics about the compilation",
436 "runtimeStats", "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
437 "supportCall", "Keep a stripped down version of the symbol table in the generated code to support the call() method",
438 "runtimeClass", "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
439 "hashClass", "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
440 "unixRuntime", "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
441 "pageSize", "The page size (must be a power of two)",
442 "totalPages", "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
443 "onePage", "One page hack (FIXME: document this better)",
444 "lessConstants", "Use less constants at the cost of speed (FIXME: document this better)"
447 private Option getOption(String name) {
448 name = name.toLowerCase();
450 for(int i=0;i<options.length;i+=2)
451 if(options[i].toLowerCase().equals(name))
452 return new Option(options[i]);
454 } catch(NoSuchFieldException e) {
459 public void parseOptions(String opts) {
460 if(opts == null || opts.length() == 0) return;
461 StringTokenizer st = new StringTokenizer(opts,",");
462 while(st.hasMoreElements()) {
463 String tok = st.nextToken();
466 if(tok.indexOf("=") != -1) {
467 key = tok.substring(0,tok.indexOf("="));
468 val = tok.substring(tok.indexOf("=")+1);
469 } else if(tok.startsWith("no")) {
470 key = tok.substring(2);
476 Option opt = getOption(key);
478 System.err.println("WARNING: No such option: " + key);
482 if(opt.getType() == String.class)
484 else if(opt.getType() == Integer.TYPE)
486 opt.set(parseInt(val));
487 } catch(NumberFormatException e) {
488 System.err.println("WARNING: " + val + " is not an integer");
490 else if(opt.getType() == Boolean.TYPE)
491 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
493 throw new Error("Unknown type: " + opt.getType());
497 private static Integer parseInt(String s) {
500 if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
501 else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
503 if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
504 else n = Integer.parseInt(s);
505 return new Integer(n*mult);
508 private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
509 StringTokenizer st = new StringTokenizer(s," ");
510 StringBuffer sb = new StringBuffer();
511 for(int i=0;i<firstindent;i++)
514 while(st.hasMoreTokens()) {
515 String tok = st.nextToken();
516 if(tok.length() + sofar + 1 > width && sofar > 0) {
518 for(int i=0;i<indent;i++) sb.append(' ');
520 } else if(sofar > 0) {
525 sofar += tok.length();
528 return sb.toString();
531 // This ugliness is to work around a gcj static linking bug (Bug 12908)
532 // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
533 protected static String dateTime() {
535 return new Date().toString();
536 } catch(RuntimeException e) {