1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
8 import org.xwt.mips.util.SeekableData;
9 import org.xwt.mips.util.SeekableFile;
11 public abstract class Compiler implements Registers {
12 /** The ELF binary being read */
15 /** The name of the class beging generated */
16 protected final String fullClassName;
18 /** The name of the binary this class is begin generated from */
19 protected String source = "unknown.mips.binary";
20 public void setSource(String source) { this.source = source; }
22 /** Thrown when the compilation fails for some reason */
23 protected static class Exn extends Exception { public Exn(String s) { super(s); } }
25 // Set this to true to enable fast memory access
26 // When this is enabled a Java RuntimeException will be thrown when a page fault occures. When it is disabled
27 // a FaultException will be throw which is easier to catch and deal with, however. as the name implies, this is slower
28 protected boolean fastMem = true;
30 // This MUST be a power of two. If it is not horrible things will happen
31 // NOTE: This value can be much higher without breaking the classfile
32 // specs (around 1024) but Hotstop seems to do much better with smaller
34 protected int maxInsnPerMethod = 128;
37 protected int maxBytesPerMethod;
38 protected int methodMask;
39 protected int methodShift;
40 protected void maxInsnPerMethodInit() throws Exn {
41 if((maxInsnPerMethod&(maxInsnPerMethod-1)) != 0) throw new Exn("maxBytesPerMethod is not a power of two");
42 maxBytesPerMethod = maxInsnPerMethod*4;
43 methodMask = ~(maxBytesPerMethod-1);
44 while(maxBytesPerMethod>>>methodShift != 1) methodShift++;
47 // True to try to determine which case statement are needed and only include them
48 protected boolean pruneCases = true;
50 protected boolean assumeTailCalls = true;
52 protected boolean optimizedMemcpy = true;
54 // True to insert some code in the output to help diagnore compiler problems
55 protected boolean debugCompiler = false;
57 // True to print various statistics about the compilation
58 protected boolean printStats = false;
60 // True to generate runtime statistics that slow execution down significantly
61 protected boolean runtimeStats = false;
63 protected boolean supportCall = true;
65 protected boolean nullPointerCheck = false;
67 protected String runtimeClass = "org.xwt.mips.Runtime";
69 protected String hashClass = "java.util.Hashtable";
71 protected boolean unixRuntime;
73 protected boolean lessConstants = false;
75 protected int pageSize = 4096;
76 protected int totalPages = 65536;
77 protected int pageShift;
78 protected boolean onePage;
80 protected void pageSizeInit() throws Exn {
82 Runtime.checkPageSize(pageSize,totalPages);
83 } catch(IllegalArgumentException e) {
84 throw new Exn(e.getMessage());
86 while(pageSize>>>pageShift != 1) pageShift++;
89 /** The address of the memcpy function in the binary (used for optimizedMemcpy) */
92 /** The address of the memset function in the binary (used for optimizedMemcpy) */
95 /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
96 protected Set jumpableAddresses;
98 /** Some important symbols */
99 ELF.Symbol userInfo, gp;
101 private static void usage() {
102 System.err.println("Usage: java Compiler [-outfile output.java] [-o options] [-dumpoptions] <classname> <binary.mips>");
103 System.err.println("-o takes mount(8) like options and can be specified multiple times");
104 System.err.println("Available options:");
105 for(int i=0;i<options.length;i+=2)
106 System.err.print(options[i] + ": " + wrapAndIndent(options[i+1],18-2-options[i].length(),18,62));
110 public static void main(String[] args) throws IOException {
111 String outfile = null;
113 String className = null;
114 String mipsBinaryFileName = null;
115 String outformat = null;
116 boolean dumpOptions = false;
118 while(args.length-arg > 0) {
119 if(args[arg].equals("-outfile")) {
121 if(arg==args.length) usage();
123 } else if(args[arg].equals("-outformat")) {
125 if(arg==args.length) usage();
126 outformat = args[arg];
127 } else if(args[arg].equals("-o")) {
129 if(arg==args.length) usage();
130 if(o==null || o.length() == 0)
132 else if(args[arg].length() != 0)
133 o += "," + args[arg];
134 } else if(args[arg].equals("-dumpoptions")) {
136 } else if(className == null) {
137 className = args[arg];
138 } else if(mipsBinaryFileName == null) {
139 mipsBinaryFileName = args[arg];
145 if(className == null || mipsBinaryFileName == null) usage();
147 SeekableData mipsBinary = new SeekableFile(mipsBinaryFileName);
150 OutputStream os = null;
151 Compiler comp = null;
152 if(outformat == null || outformat.equals("class")) {
153 if(outfile == null) {
154 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
157 os = new FileOutputStream(outfile);
158 comp = new ClassFileCompiler(mipsBinary,className,os);
159 } else if(outformat.equals("javasource") || outformat .equals("java")) {
160 w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
161 comp = new JavaSourceCompiler(mipsBinary,className,w);
163 System.err.println("Unknown output format: " + outformat);
167 comp.parseOptions(o);
168 comp.setSource(mipsBinaryFileName);
171 System.err.println("== Options ==");
172 for(int i=0;i<options.length;i+=2)
173 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
174 System.err.println("== End Options ==");
180 System.err.println("Compiler Error: " + e.getMessage());
183 if(w != null) w.close();
184 if(os != null) os.close();
188 public Compiler(SeekableData binary, String fullClassName) throws IOException {
189 this.fullClassName = fullClassName;
190 elf = new ELF(binary);
192 if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
193 if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
194 if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
197 protected abstract void _go() throws Exn, IOException;
199 private boolean used;
200 public void go() throws Exn, IOException {
201 if(used) throw new RuntimeException("Compiler instances are good for one shot only");
204 if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
205 if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
206 if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
207 if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
208 if(onePage) totalPages = 1;
210 maxInsnPerMethodInit();
213 // Get a copy of the symbol table in the elf binary
214 ELF.Symtab symtab = elf.getSymtab();
215 if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
218 // Check for some functions we can override
219 sym = symtab.getGlobalSymbol("memcpy");
220 memcpy = sym == null ? -1 : sym.addr;
222 sym = symtab.getGlobalSymbol("memset");
223 memset = sym == null ? -1 : sym.addr;
225 userInfo = symtab.getGlobalSymbol("user_info");
226 gp = symtab.getGlobalSymbol("_gp");
227 if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");
230 // Find all possible branches
231 jumpableAddresses = new HashSet();
233 jumpableAddresses.add(new Integer(elf.header.entry));
235 ELF.SHeader text = elf.sectionWithName(".text");
236 if(text == null) throw new Exn("No .text segment");
238 findBranchesInSymtab(symtab,jumpableAddresses);
240 for(int i=0;i<elf.sheaders.length;i++) {
241 ELF.SHeader sheader = elf.sheaders[i];
242 String name = sheader.name;
243 // if this section doesn't get loaded into our address space don't worry about it
244 if(sheader.addr == 0x0) continue;
245 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
246 findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
249 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
252 if(unixRuntime && runtimeClass.startsWith("org.xwt.mips.")) runtimeClass = "org.xwt.mips.UnixRuntime";
254 for(int i=0;i<elf.sheaders.length;i++) {
255 String name = elf.sheaders[i].name;
256 if(elf.sheaders[i].addr != 0 && !(
257 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
258 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
259 throw new Exn("Unknown section: " + name);
264 private void findBranchesInSymtab(ELF.Symtab symtab, Set jumps) {
265 ELF.Symbol[] symbols = symtab.symbols;
267 for(int i=0;i<symbols.length;i++) {
268 ELF.Symbol s = symbols[i];
269 if(s.type == ELF.Symbol.STT_FUNC) {
270 if(jumps.add(new Integer(s.addr))) {
271 //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
276 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
279 private void findBranchesInText(int base, DataInputStream dis, int size, Set jumps) throws IOException {
283 int[] lui_val = new int[32];
284 int[] lui_pc = new int[32];
285 //Interpreter inter = new Interpreter(source);
287 for(int i=0;i<count;i++,pc+=4) {
288 int insn = dis.readInt();
289 int op = (insn >>> 26) & 0xff;
290 int rs = (insn >>> 21) & 0x1f;
291 int rt = (insn >>> 16) & 0x1f;
292 int signedImmediate = (insn << 16) >> 16;
293 int unsignedImmediate = insn & 0xffff;
294 int branchTarget = signedImmediate;
295 int jumpTarget = (insn & 0x03ffffff);
296 int subcode = insn & 0x3f;
302 if(jumps.add(new Integer(pc+8))) n++; // return address
305 if(jumps.add(new Integer(pc+4))) n++;
313 if(jumps.add(new Integer(pc+8))) n++; // return address
317 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
322 if(jumps.add(new Integer(pc+8))) n++; // return address
325 if(jumps.add(new Integer((pc&0xf0000000)|(jumpTarget << 2)))) n++;
331 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
334 if(pc - lui_pc[rs] <= 4*32) {
335 int t = (lui_val[rs]<<16)+signedImmediate;
336 if((t&3)==0 && t >= base && t < base+size) {
337 if(jumps.add(new Integer(t))) {
338 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
342 // we just blew it away
343 if(rt == rs) lui_pc[rs] = 0;
348 lui_val[rt] = unsignedImmediate;
353 case 17: // FPU Instructions
355 case 8: // BC1F, BC1T
356 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
363 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
366 private void findBranchesInData(DataInputStream dis, int size, Set jumps, int textStart, int textEnd) throws IOException {
369 for(int i=0;i<count;i++) {
370 int word = dis.readInt();
371 if((word&3)==0 && word >= textStart && word < textEnd) {
372 if(jumps.add(new Integer(word))) {
373 //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
379 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
382 // Helper functions for pretty output
383 protected final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
384 protected final static String toHex8(int n) {
385 String s = Long.toString(n & 0xffffffffL, 16);
386 StringBuffer sb = new StringBuffer("0x");
387 for(int i=8-s.length();i>0;i--) sb.append('0');
389 return sb.toString();
392 protected final static String toOctal3(int n) {
393 char[] buf = new char[3];
394 for(int i=2;i>=0;i--) {
395 buf[i] = (char) ('0' + (n & 7));
398 return new String(buf);
402 private class Option {
403 private java.lang.reflect.Field field;
404 public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
405 public void set(Object val) {
406 if(field == null) return;
408 field.setAccessible(true);
409 field.set(Compiler.this,val);
410 } catch(IllegalAccessException e) {
411 System.err.println(e);
414 public Object get() {
415 if(field == null) return null;
417 field.setAccessible(true);
418 return field.get(Compiler.this);
419 } catch(IllegalAccessException e) {
420 System.err.println(e); return null;
423 public Class getType() { return field == null ? null : field.getType(); }
426 private static String[] options = {
427 "fastMem", "Enable fast memory access - RuntimeExceptions will be thrown on faults",
428 "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
429 "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
430 "pruneCases", "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
431 "assumeTailCalls", "Assume the JIT optimizes tail calls",
432 "optimizedMemcpy", "Use an optimized java version of memcpy where possible",
433 "debugCompiler", "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
434 "printStats", "Output some useful statistics about the compilation",
435 "runtimeStats", "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
436 "supportCall", "Keep a stripped down version of the symbol table in the generated code to support the call() method",
437 "runtimeClass", "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
438 "hashClass", "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
439 "unixRuntime", "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
440 "pageSize", "The page size (must be a power of two)",
441 "totalPages", "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
442 "onePage", "One page hack (FIXME: document this better)",
443 "lessConstants", "Use less constants at the cost of speed (FIXME: document this better)"
446 private Option getOption(String name) {
447 name = name.toLowerCase();
449 for(int i=0;i<options.length;i+=2)
450 if(options[i].toLowerCase().equals(name))
451 return new Option(options[i]);
453 } catch(NoSuchFieldException e) {
458 public void parseOptions(String opts) {
459 if(opts == null || opts.length() == 0) return;
460 StringTokenizer st = new StringTokenizer(opts,",");
461 while(st.hasMoreElements()) {
462 String tok = st.nextToken();
465 if(tok.indexOf("=") != -1) {
466 key = tok.substring(0,tok.indexOf("="));
467 val = tok.substring(tok.indexOf("=")+1);
468 } else if(tok.startsWith("no")) {
469 key = tok.substring(2);
475 Option opt = getOption(key);
477 System.err.println("WARNING: No such option: " + key);
481 if(opt.getType() == String.class)
483 else if(opt.getType() == Integer.TYPE)
485 opt.set(parseInt(val));
486 } catch(NumberFormatException e) {
487 System.err.println("WARNING: " + val + " is not an integer");
489 else if(opt.getType() == Boolean.TYPE)
490 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
492 throw new Error("Unknown type: " + opt.getType());
496 private static Integer parseInt(String s) {
499 if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
500 else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
502 if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
503 else n = Integer.parseInt(s);
504 return new Integer(n*mult);
507 private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
508 StringTokenizer st = new StringTokenizer(s," ");
509 StringBuffer sb = new StringBuffer();
510 for(int i=0;i<firstindent;i++)
513 while(st.hasMoreTokens()) {
514 String tok = st.nextToken();
515 if(tok.length() + sofar + 1 > width && sofar > 0) {
517 for(int i=0;i<indent;i++) sb.append(' ');
519 } else if(sofar > 0) {
524 sofar += tok.length();
527 return sb.toString();
530 // This ugliness is to work around a gcj static linking bug (Bug 12908)
531 // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
532 protected static String dateTime() {
534 return new Date().toString();
535 } catch(RuntimeException e) {