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 protected final String fullClassName;
17 /** The name of the binary this class is begin generated from */
18 protected String source = "unknown.mips.binary";
19 public void setSource(String source) { this.source = source; }
21 /** Thrown when the compilation fails for some reason */
22 protected 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 protected 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 protected int maxInsnPerMethod = 128;
36 protected int maxBytesPerMethod;
37 protected int methodMask;
38 protected int methodShift;
39 protected 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 protected boolean pruneCases = true;
49 protected boolean assumeTailCalls = true;
51 protected boolean optimizedMemcpy = true;
53 // True to insert some code in the output to help diagnore compiler problems
54 protected boolean debugCompiler = false;
56 // True to print various statistics about the compilation
57 protected boolean printStats = false;
59 // True to generate runtime statistics that slow execution down significantly
60 protected boolean runtimeStats = false;
62 protected boolean supportCall = true;
64 protected boolean nullPointerCheck = false;
66 protected String runtimeClass = "org.ibex.nestedvm.Runtime";
68 protected String hashClass = "java.util.Hashtable";
70 protected boolean unixRuntime;
72 protected boolean lessConstants = false;
74 protected int pageSize = 4096;
75 protected int totalPages = 65536;
76 protected int pageShift;
77 protected boolean onePage;
79 protected void pageSizeInit() throws Exn {
81 Runtime.checkPageSize(pageSize,totalPages);
82 } catch(IllegalArgumentException e) {
83 throw new Exn(e.getMessage());
85 while(pageSize>>>pageShift != 1) pageShift++;
88 /** The address of the memcpy function in the binary (used for optimizedMemcpy) */
91 /** The address of the memset function in the binary (used for optimizedMemcpy) */
94 /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
95 protected Set jumpableAddresses;
97 /** Some important symbols */
98 ELF.Symbol userInfo, gp;
100 private static void usage() {
101 System.err.println("Usage: java Compiler [-outfile output.java] [-o options] [-dumpoptions] <classname> <binary.mips>");
102 System.err.println("-o takes mount(8) like options and can be specified multiple times");
103 System.err.println("Available options:");
104 for(int i=0;i<options.length;i+=2)
105 System.err.print(options[i] + ": " + wrapAndIndent(options[i+1],18-2-options[i].length(),18,62));
109 public static void main(String[] args) throws IOException {
110 String outfile = null;
112 String className = null;
113 String mipsBinaryFileName = null;
114 String outformat = null;
115 boolean dumpOptions = false;
117 while(args.length-arg > 0) {
118 if(args[arg].equals("-outfile")) {
120 if(arg==args.length) usage();
122 } else if(args[arg].equals("-outformat")) {
124 if(arg==args.length) usage();
125 outformat = args[arg];
126 } else if(args[arg].equals("-o")) {
128 if(arg==args.length) usage();
129 if(o==null || o.length() == 0)
131 else if(args[arg].length() != 0)
132 o += "," + args[arg];
133 } else if(args[arg].equals("-dumpoptions")) {
135 } else if(className == null) {
136 className = args[arg];
137 } else if(mipsBinaryFileName == null) {
138 mipsBinaryFileName = args[arg];
144 if(className == null || mipsBinaryFileName == null) usage();
146 Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
149 OutputStream os = null;
150 Compiler comp = null;
151 if(outformat == null || outformat.equals("class")) {
152 if(outfile == null) {
153 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
156 os = new FileOutputStream(outfile);
157 comp = new ClassFileCompiler(mipsBinary,className,os);
158 } else if(outformat.equals("javasource") || outformat .equals("java")) {
159 w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
160 comp = new JavaSourceCompiler(mipsBinary,className,w);
162 System.err.println("Unknown output format: " + outformat);
166 comp.parseOptions(o);
167 comp.setSource(mipsBinaryFileName);
170 System.err.println("== Options ==");
171 for(int i=0;i<options.length;i+=2)
172 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
173 System.err.println("== End Options ==");
179 System.err.println("Compiler Error: " + e.getMessage());
182 if(w != null) w.close();
183 if(os != null) os.close();
187 public Compiler(Seekable binary, String fullClassName) throws IOException {
188 this.fullClassName = fullClassName;
189 elf = new ELF(binary);
191 if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
192 if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
193 if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
196 protected abstract void _go() throws Exn, IOException;
198 private boolean used;
199 public void go() throws Exn, IOException {
200 if(used) throw new RuntimeException("Compiler instances are good for one shot only");
203 if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
204 if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
205 if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
206 if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
207 if(onePage) totalPages = 1;
209 maxInsnPerMethodInit();
212 // Get a copy of the symbol table in the elf binary
213 ELF.Symtab symtab = elf.getSymtab();
214 if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
217 // Check for some functions we can override
218 sym = symtab.getGlobalSymbol("memcpy");
219 memcpy = sym == null ? -1 : sym.addr;
221 sym = symtab.getGlobalSymbol("memset");
222 memset = sym == null ? -1 : sym.addr;
224 userInfo = symtab.getGlobalSymbol("user_info");
225 gp = symtab.getGlobalSymbol("_gp");
226 if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");
229 // Find all possible branches
230 jumpableAddresses = new HashSet();
232 jumpableAddresses.add(new Integer(elf.header.entry));
234 ELF.SHeader text = elf.sectionWithName(".text");
235 if(text == null) throw new Exn("No .text segment");
237 findBranchesInSymtab(symtab,jumpableAddresses);
239 for(int i=0;i<elf.sheaders.length;i++) {
240 ELF.SHeader sheader = elf.sheaders[i];
241 String name = sheader.name;
242 // if this section doesn't get loaded into our address space don't worry about it
243 if(sheader.addr == 0x0) continue;
244 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
245 findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
248 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
251 if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
253 for(int i=0;i<elf.sheaders.length;i++) {
254 String name = elf.sheaders[i].name;
255 if(elf.sheaders[i].addr != 0 && !(
256 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
257 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
258 throw new Exn("Unknown section: " + name);
263 private void findBranchesInSymtab(ELF.Symtab symtab, Set jumps) {
264 ELF.Symbol[] symbols = symtab.symbols;
266 for(int i=0;i<symbols.length;i++) {
267 ELF.Symbol s = symbols[i];
268 if(s.type == ELF.Symbol.STT_FUNC) {
269 if(jumps.add(new Integer(s.addr))) {
270 //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
275 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
278 private void findBranchesInText(int base, DataInputStream dis, int size, Set jumps) throws IOException {
282 int[] lui_val = new int[32];
283 int[] lui_pc = new int[32];
284 //Interpreter inter = new Interpreter(source);
286 for(int i=0;i<count;i++,pc+=4) {
287 int insn = dis.readInt();
288 int op = (insn >>> 26) & 0xff;
289 int rs = (insn >>> 21) & 0x1f;
290 int rt = (insn >>> 16) & 0x1f;
291 int signedImmediate = (insn << 16) >> 16;
292 int unsignedImmediate = insn & 0xffff;
293 int branchTarget = signedImmediate;
294 int jumpTarget = (insn & 0x03ffffff);
295 int subcode = insn & 0x3f;
301 if(jumps.add(new Integer(pc+8))) n++; // return address
304 if(jumps.add(new Integer(pc+4))) n++;
312 if(jumps.add(new Integer(pc+8))) n++; // return address
316 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
321 if(jumps.add(new Integer(pc+8))) n++; // return address
324 if(jumps.add(new Integer((pc&0xf0000000)|(jumpTarget << 2)))) n++;
330 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
333 if(pc - lui_pc[rs] <= 4*32) {
334 int t = (lui_val[rs]<<16)+signedImmediate;
335 if((t&3)==0 && t >= base && t < base+size) {
336 if(jumps.add(new Integer(t))) {
337 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
341 // we just blew it away
342 if(rt == rs) lui_pc[rs] = 0;
347 lui_val[rt] = unsignedImmediate;
352 case 17: // FPU Instructions
354 case 8: // BC1F, BC1T
355 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
362 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
365 private void findBranchesInData(DataInputStream dis, int size, Set jumps, int textStart, int textEnd) throws IOException {
368 for(int i=0;i<count;i++) {
369 int word = dis.readInt();
370 if((word&3)==0 && word >= textStart && word < textEnd) {
371 if(jumps.add(new Integer(word))) {
372 //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
378 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
381 // Helper functions for pretty output
382 protected final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
383 protected final static String toHex8(int n) {
384 String s = Long.toString(n & 0xffffffffL, 16);
385 StringBuffer sb = new StringBuffer("0x");
386 for(int i=8-s.length();i>0;i--) sb.append('0');
388 return sb.toString();
391 protected final static String toOctal3(int n) {
392 char[] buf = new char[3];
393 for(int i=2;i>=0;i--) {
394 buf[i] = (char) ('0' + (n & 7));
397 return new String(buf);
401 private class Option {
402 private java.lang.reflect.Field field;
403 public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
404 public void set(Object val) {
405 if(field == null) return;
407 field.setAccessible(true);
408 field.set(Compiler.this,val);
409 } catch(IllegalAccessException e) {
410 System.err.println(e);
413 public Object get() {
414 if(field == null) return null;
416 field.setAccessible(true);
417 return field.get(Compiler.this);
418 } catch(IllegalAccessException e) {
419 System.err.println(e); return null;
422 public Class getType() { return field == null ? null : field.getType(); }
425 private static String[] options = {
426 "fastMem", "Enable fast memory access - RuntimeExceptions will be thrown on faults",
427 "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
428 "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
429 "pruneCases", "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
430 "assumeTailCalls", "Assume the JIT optimizes tail calls",
431 "optimizedMemcpy", "Use an optimized java version of memcpy where possible",
432 "debugCompiler", "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
433 "printStats", "Output some useful statistics about the compilation",
434 "runtimeStats", "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
435 "supportCall", "Keep a stripped down version of the symbol table in the generated code to support the call() method",
436 "runtimeClass", "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
437 "hashClass", "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
438 "unixRuntime", "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
439 "pageSize", "The page size (must be a power of two)",
440 "totalPages", "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
441 "onePage", "One page hack (FIXME: document this better)",
442 "lessConstants", "Use less constants at the cost of speed (FIXME: document this better)"
445 private Option getOption(String name) {
446 name = name.toLowerCase();
448 for(int i=0;i<options.length;i+=2)
449 if(options[i].toLowerCase().equals(name))
450 return new Option(options[i]);
452 } catch(NoSuchFieldException e) {
457 public void parseOptions(String opts) {
458 if(opts == null || opts.length() == 0) return;
459 StringTokenizer st = new StringTokenizer(opts,",");
460 while(st.hasMoreElements()) {
461 String tok = st.nextToken();
464 if(tok.indexOf("=") != -1) {
465 key = tok.substring(0,tok.indexOf("="));
466 val = tok.substring(tok.indexOf("=")+1);
467 } else if(tok.startsWith("no")) {
468 key = tok.substring(2);
474 Option opt = getOption(key);
476 System.err.println("WARNING: No such option: " + key);
480 if(opt.getType() == String.class)
482 else if(opt.getType() == Integer.TYPE)
484 opt.set(parseInt(val));
485 } catch(NumberFormatException e) {
486 System.err.println("WARNING: " + val + " is not an integer");
488 else if(opt.getType() == Boolean.TYPE)
489 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
491 throw new Error("Unknown type: " + opt.getType());
495 private static Integer parseInt(String s) {
498 if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
499 else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
501 if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
502 else n = Integer.parseInt(s);
503 return new Integer(n*mult);
506 private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
507 StringTokenizer st = new StringTokenizer(s," ");
508 StringBuffer sb = new StringBuffer();
509 for(int i=0;i<firstindent;i++)
512 while(st.hasMoreTokens()) {
513 String tok = st.nextToken();
514 if(tok.length() + sofar + 1 > width && sofar > 0) {
516 for(int i=0;i<indent;i++) sb.append(' ');
518 } else if(sofar > 0) {
523 sofar += tok.length();
526 return sb.toString();
529 // This ugliness is to work around a gcj static linking bug (Bug 12908)
530 // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
531 protected static String dateTime() {
533 return new Date().toString();
534 } catch(RuntimeException e) {