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 // 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 {
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 protected Set 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;
103 String className = null;
104 String mipsBinaryFileName = null;
105 String outformat = null;
106 boolean dumpOptions = false;
108 while(args.length-arg > 0) {
109 if(args[arg].equals("-outfile")) {
111 if(arg==args.length) usage();
113 } else if(args[arg].equals("-outformat")) {
115 if(arg==args.length) usage();
116 outformat = args[arg];
117 } else if(args[arg].equals("-o")) {
119 if(arg==args.length) usage();
120 if(o==null || o.length() == 0)
122 else if(args[arg].length() != 0)
123 o += "," + args[arg];
124 } else if(args[arg].equals("-dumpoptions")) {
126 } else if(className == null) {
127 className = args[arg];
128 } else if(mipsBinaryFileName == null) {
129 mipsBinaryFileName = args[arg];
135 if(className == null || mipsBinaryFileName == null) usage();
137 Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
140 OutputStream os = null;
141 Compiler comp = null;
142 if(outformat == null || outformat.equals("class")) {
143 if(outfile == null) {
144 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
147 os = new FileOutputStream(outfile);
148 comp = new ClassFileCompiler(mipsBinary,className,os);
149 } else if(outformat.equals("javasource") || outformat .equals("java")) {
150 w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
151 comp = new JavaSourceCompiler(mipsBinary,className,w);
153 System.err.println("Unknown output format: " + outformat);
157 comp.parseOptions(o);
158 comp.setSource(mipsBinaryFileName);
161 System.err.println("== Options ==");
162 for(int i=0;i<options.length;i+=2)
163 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
164 System.err.println("== End Options ==");
170 System.err.println("Compiler Error: " + e.getMessage());
173 if(w != null) w.close();
174 if(os != null) os.close();
178 public Compiler(Seekable binary, String fullClassName) throws IOException {
179 this.fullClassName = fullClassName;
180 elf = new ELF(binary);
182 if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
183 if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
184 if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
187 protected abstract void _go() throws Exn, IOException;
189 private boolean used;
190 public void go() throws Exn, IOException {
191 if(used) throw new RuntimeException("Compiler instances are good for one shot only");
194 if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
195 if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
196 if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
197 if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
198 if(onePage) totalPages = 1;
200 maxInsnPerMethodInit();
203 // Get a copy of the symbol table in the elf binary
204 ELF.Symtab symtab = elf.getSymtab();
205 if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
208 userInfo = symtab.getGlobalSymbol("user_info");
209 gp = symtab.getGlobalSymbol("_gp");
210 if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");
213 // Find all possible branches
214 jumpableAddresses = new HashSet();
216 jumpableAddresses.add(new Integer(elf.header.entry));
218 ELF.SHeader text = elf.sectionWithName(".text");
219 if(text == null) throw new Exn("No .text segment");
221 findBranchesInSymtab(symtab,jumpableAddresses);
223 for(int i=0;i<elf.sheaders.length;i++) {
224 ELF.SHeader sheader = elf.sheaders[i];
225 String name = sheader.name;
226 // if this section doesn't get loaded into our address space don't worry about it
227 if(sheader.addr == 0x0) continue;
228 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
229 findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
232 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
235 if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
237 for(int i=0;i<elf.sheaders.length;i++) {
238 String name = elf.sheaders[i].name;
239 if(elf.sheaders[i].addr != 0 && !(
240 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
241 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
242 throw new Exn("Unknown section: " + name);
247 private void findBranchesInSymtab(ELF.Symtab symtab, Set jumps) {
248 ELF.Symbol[] symbols = symtab.symbols;
250 for(int i=0;i<symbols.length;i++) {
251 ELF.Symbol s = symbols[i];
252 if(s.type == ELF.Symbol.STT_FUNC) {
253 if(jumps.add(new Integer(s.addr))) {
254 //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
259 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
262 private void findBranchesInText(int base, DataInputStream dis, int size, Set jumps) throws IOException {
266 int[] lui_val = new int[32];
267 int[] lui_pc = new int[32];
268 //Interpreter inter = new Interpreter(source);
270 for(int i=0;i<count;i++,pc+=4) {
271 int insn = dis.readInt();
272 int op = (insn >>> 26) & 0xff;
273 int rs = (insn >>> 21) & 0x1f;
274 int rt = (insn >>> 16) & 0x1f;
275 int signedImmediate = (insn << 16) >> 16;
276 int unsignedImmediate = insn & 0xffff;
277 int branchTarget = signedImmediate;
278 int jumpTarget = (insn & 0x03ffffff);
279 int subcode = insn & 0x3f;
285 if(jumps.add(new Integer(pc+8))) n++; // return address
288 if(jumps.add(new Integer(pc+4))) n++;
296 if(jumps.add(new Integer(pc+8))) n++; // return address
300 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
305 if(jumps.add(new Integer(pc+8))) n++; // return address
308 if(jumps.add(new Integer((pc&0xf0000000)|(jumpTarget << 2)))) n++;
314 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
317 if(pc - lui_pc[rs] <= 4*32) {
318 int t = (lui_val[rs]<<16)+signedImmediate;
319 if((t&3)==0 && t >= base && t < base+size) {
320 if(jumps.add(new Integer(t))) {
321 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
325 // we just blew it away
326 if(rt == rs) lui_pc[rs] = 0;
331 lui_val[rt] = unsignedImmediate;
336 case 17: // FPU Instructions
338 case 8: // BC1F, BC1T
339 if(jumps.add(new Integer(pc+branchTarget*4+4))) n++;
346 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
349 private void findBranchesInData(DataInputStream dis, int size, Set jumps, int textStart, int textEnd) throws IOException {
352 for(int i=0;i<count;i++) {
353 int word = dis.readInt();
354 if((word&3)==0 && word >= textStart && word < textEnd) {
355 if(jumps.add(new Integer(word))) {
356 //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
362 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
365 // Helper functions for pretty output
366 protected final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
367 protected final static String toHex8(int n) {
368 String s = Long.toString(n & 0xffffffffL, 16);
369 StringBuffer sb = new StringBuffer("0x");
370 for(int i=8-s.length();i>0;i--) sb.append('0');
372 return sb.toString();
375 protected final static String toOctal3(int n) {
376 char[] buf = new char[3];
377 for(int i=2;i>=0;i--) {
378 buf[i] = (char) ('0' + (n & 7));
381 return new String(buf);
385 private class Option {
386 private java.lang.reflect.Field field;
387 public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
388 public void set(Object val) {
389 if(field == null) return;
391 field.setAccessible(true);
392 field.set(Compiler.this,val);
393 } catch(IllegalAccessException e) {
394 System.err.println(e);
397 public Object get() {
398 if(field == null) return null;
400 field.setAccessible(true);
401 return field.get(Compiler.this);
402 } catch(IllegalAccessException e) {
403 System.err.println(e); return null;
406 public Class getType() { return field == null ? null : field.getType(); }
409 private static String[] options = {
410 "fastMem", "Enable fast memory access - RuntimeExceptions will be thrown on faults",
411 "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
412 "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
413 "pruneCases", "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
414 "assumeTailCalls", "Assume the JIT optimizes tail calls",
415 "optimizedMemcpy", "Use an optimized java version of memcpy where possible",
416 "debugCompiler", "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
417 "printStats", "Output some useful statistics about the compilation",
418 "runtimeStats", "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
419 "supportCall", "Keep a stripped down version of the symbol table in the generated code to support the call() method",
420 "runtimeClass", "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
421 "hashClass", "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
422 "unixRuntime", "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
423 "pageSize", "The page size (must be a power of two)",
424 "totalPages", "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
425 "onePage", "One page hack (FIXME: document this better)",
426 "lessConstants", "Use less constants at the cost of speed (FIXME: document this better)"
429 private Option getOption(String name) {
430 name = name.toLowerCase();
432 for(int i=0;i<options.length;i+=2)
433 if(options[i].toLowerCase().equals(name))
434 return new Option(options[i]);
436 } catch(NoSuchFieldException e) {
441 public void parseOptions(String opts) {
442 if(opts == null || opts.length() == 0) return;
443 StringTokenizer st = new StringTokenizer(opts,",");
444 while(st.hasMoreElements()) {
445 String tok = st.nextToken();
448 if(tok.indexOf("=") != -1) {
449 key = tok.substring(0,tok.indexOf("="));
450 val = tok.substring(tok.indexOf("=")+1);
451 } else if(tok.startsWith("no")) {
452 key = tok.substring(2);
458 Option opt = getOption(key);
460 System.err.println("WARNING: No such option: " + key);
464 if(opt.getType() == String.class)
466 else if(opt.getType() == Integer.TYPE)
468 opt.set(parseInt(val));
469 } catch(NumberFormatException e) {
470 System.err.println("WARNING: " + val + " is not an integer");
472 else if(opt.getType() == Boolean.TYPE)
473 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
475 throw new Error("Unknown type: " + opt.getType());
479 private static Integer parseInt(String s) {
482 if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
483 else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
485 if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
486 else n = Integer.parseInt(s);
487 return new Integer(n*mult);
490 private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
491 StringTokenizer st = new StringTokenizer(s," ");
492 StringBuffer sb = new StringBuffer();
493 for(int i=0;i<firstindent;i++)
496 while(st.hasMoreTokens()) {
497 String tok = st.nextToken();
498 if(tok.length() + sofar + 1 > width && sofar > 0) {
500 for(int i=0;i<indent;i++) sb.append(' ');
502 } else if(sofar > 0) {
507 sofar += tok.length();
510 return sb.toString();
513 // This ugliness is to work around a gcj static linking bug (Bug 12908)
514 // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
515 protected static String dateTime() {
517 return new Date().toString();
518 } catch(RuntimeException e) {