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 final String fullClassName;
19 /** The name of the binary this class is begin generated from */
20 String source = "unknown.mips.binary";
21 public void setSource(String source) { this.source = source; }
23 /** Thrown when the compilation fails for some reason */
24 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 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 int maxInsnPerMethod = 128;
38 int maxBytesPerMethod;
41 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 boolean pruneCases = true;
51 boolean assumeTailCalls = true;
53 // True to insert some code in the output to help diagnore compiler problems
54 boolean debugCompiler = false;
56 // True to print various statistics about the compilation
57 boolean printStats = false;
59 // True to generate runtime statistics that slow execution down significantly
60 boolean runtimeStats = false;
62 boolean supportCall = true;
64 boolean nullPointerCheck = false;
66 String runtimeClass = "org.ibex.nestedvm.Runtime";
68 String hashClass = "java.util.Hashtable";
72 boolean lessConstants;
77 int totalPages = 65536;
81 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 /** A set of all addresses that can be jumped too (only available if pruneCases == true) */
88 Hashtable jumpableAddresses;
90 /** Some important symbols */
91 ELF.Symbol userInfo, gp;
93 private static void usage() {
94 System.err.println("Usage: java Compiler [-outfile output.java] [-o options] [-dumpoptions] <classname> <binary.mips>");
95 System.err.println("-o takes mount(8) like options and can be specified multiple times");
96 System.err.println("Available options:");
97 for(int i=0;i<options.length;i+=2)
98 System.err.print(options[i] + ": " + wrapAndIndent(options[i+1],18-2-options[i].length(),18,62));
102 public static void main(String[] args) throws IOException {
103 String outfile = null;
105 String className = null;
106 String mipsBinaryFileName = null;
107 String outformat = null;
108 boolean dumpOptions = false;
110 while(args.length-arg > 0) {
111 if(args[arg].equals("-outfile")) {
113 if(arg==args.length) usage();
115 } else if(args[arg].equals("-outformat")) {
117 if(arg==args.length) usage();
118 outformat = args[arg];
119 } else if(args[arg].equals("-o")) {
121 if(arg==args.length) usage();
122 if(o==null || o.length() == 0)
124 else if(args[arg].length() != 0)
125 o += "," + args[arg];
126 } else if(args[arg].equals("-dumpoptions")) {
128 } else if(className == null) {
129 className = args[arg];
130 } else if(mipsBinaryFileName == null) {
131 mipsBinaryFileName = args[arg];
137 if(className == null || mipsBinaryFileName == null) usage();
139 Seekable mipsBinary = new Seekable.File(mipsBinaryFileName);
142 OutputStream os = null;
143 Compiler comp = null;
144 if(outformat == null || outformat.equals("class")) {
145 if(outfile == null) {
146 System.err.println("Refusing to write a classfile to stdout - use -outfile foo.class");
149 os = new FileOutputStream(outfile);
150 comp = new ClassFileCompiler(mipsBinary,className,os);
151 } else if(outformat.equals("javasource") || outformat .equals("java")) {
152 w = outfile == null ? new OutputStreamWriter(System.out): new FileWriter(outfile);
153 comp = new JavaSourceCompiler(mipsBinary,className,w);
155 System.err.println("Unknown output format: " + outformat);
159 comp.parseOptions(o);
160 comp.setSource(mipsBinaryFileName);
163 System.err.println("== Options ==");
164 for(int i=0;i<options.length;i+=2)
165 System.err.println(options[i] + ": " + comp.getOption(options[i]).get());
166 System.err.println("== End Options ==");
172 System.err.println("Compiler Error: " + e.getMessage());
175 if(w != null) w.close();
176 if(os != null) os.close();
180 public Compiler(Seekable binary, String fullClassName) throws IOException {
181 this.fullClassName = fullClassName;
182 elf = new ELF(binary);
184 if(elf.header.type != ELF.ELFHeader.ET_EXEC) throw new IOException("Binary is not an executable");
185 if(elf.header.machine != ELF.ELFHeader.EM_MIPS) throw new IOException("Binary is not for the MIPS I Architecture");
186 if(elf.ident.data != ELF.ELFIdent.ELFDATA2MSB) throw new IOException("Binary is not big endian");
189 abstract void _go() throws Exn, IOException;
191 private boolean used;
192 public void go() throws Exn, IOException {
193 if(used) throw new RuntimeException("Compiler instances are good for one shot only");
196 if(onePage && pageSize <= 4096) pageSize = 4*1024*1024;
197 if(nullPointerCheck && !fastMem) throw new Exn("fastMem must be enabled for nullPointerCheck to be of any use");
198 if(onePage && !fastMem) throw new Exn("fastMem must be enabled for onePage to be of any use");
199 if(totalPages == 1 && !onePage) throw new Exn("totalPages == 1 and onePage is not set");
200 if(onePage) totalPages = 1;
202 maxInsnPerMethodInit();
205 // Get a copy of the symbol table in the elf binary
206 ELF.Symtab symtab = elf.getSymtab();
207 if(symtab == null) throw new Exn("Binary has no symtab (did you strip it?)");
210 userInfo = symtab.getGlobalSymbol("user_info");
211 gp = symtab.getGlobalSymbol("_gp");
212 if(gp == null) throw new Exn("no _gp symbol (did you strip the binary?)");
215 // Find all possible branches
216 jumpableAddresses = new Hashtable();
218 jumpableAddresses.put(new Integer(elf.header.entry),Boolean.TRUE);
220 ELF.SHeader text = elf.sectionWithName(".text");
221 if(text == null) throw new Exn("No .text segment");
223 findBranchesInSymtab(symtab,jumpableAddresses);
225 for(int i=0;i<elf.sheaders.length;i++) {
226 ELF.SHeader sheader = elf.sheaders[i];
227 String name = sheader.name;
228 // if this section doesn't get loaded into our address space don't worry about it
229 if(sheader.addr == 0x0) continue;
230 if(name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") || name.equals(".ctors") || name.equals(".dtors"))
231 findBranchesInData(new DataInputStream(sheader.getInputStream()),sheader.size,jumpableAddresses,text.addr,text.addr+text.size);
234 findBranchesInText(text.addr,new DataInputStream(text.getInputStream()),text.size,jumpableAddresses);
237 if(unixRuntime && runtimeClass.startsWith("org.ibex.nestedvm.")) runtimeClass = "org.ibex.nestedvm.UnixRuntime";
239 for(int i=0;i<elf.sheaders.length;i++) {
240 String name = elf.sheaders[i].name;
241 if(elf.sheaders[i].addr != 0 && !(
242 name.equals(".text")|| name.equals(".data") || name.equals(".sdata") || name.equals(".rodata") ||
243 name.equals(".ctors") || name.equals(".dtors") || name.equals(".bss") || name.equals(".sbss")))
244 throw new Exn("Unknown section: " + name);
249 private void findBranchesInSymtab(ELF.Symtab symtab, Hashtable jumps) {
250 ELF.Symbol[] symbols = symtab.symbols;
252 for(int i=0;i<symbols.length;i++) {
253 ELF.Symbol s = symbols[i];
254 if(s.type == ELF.Symbol.STT_FUNC) {
255 if(jumps.put(new Integer(s.addr),Boolean.TRUE) == null) {
256 //System.err.println("Adding symbol from symtab: " + s.name + " at " + toHex(s.addr));
261 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Symtab");
264 private void findBranchesInText(int base, DataInputStream dis, int size, Hashtable jumps) throws IOException {
268 int[] lui_val = new int[32];
269 int[] lui_pc = new int[32];
270 //Interpreter inter = new Interpreter(source);
272 for(int i=0;i<count;i++,pc+=4) {
273 int insn = dis.readInt();
274 int op = (insn >>> 26) & 0xff;
275 int rs = (insn >>> 21) & 0x1f;
276 int rt = (insn >>> 16) & 0x1f;
277 int signedImmediate = (insn << 16) >> 16;
278 int unsignedImmediate = insn & 0xffff;
279 int branchTarget = signedImmediate;
280 int jumpTarget = (insn & 0x03ffffff);
281 int subcode = insn & 0x3f;
287 if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
290 if(jumps.put(new Integer(pc+4),Boolean.TRUE) == null) n++;
298 if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
302 if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
307 if(jumps.put(new Integer(pc+8),Boolean.TRUE) == null) n++; // return address
310 if(jumps.put(new Integer((pc&0xf0000000)|(jumpTarget << 2)),Boolean.TRUE) == null) n++;
316 if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
319 if(pc - lui_pc[rs] <= 4*32) {
320 int t = (lui_val[rs]<<16)+signedImmediate;
321 if((t&3)==0 && t >= base && t < base+size) {
322 if(jumps.put(new Integer(t),Boolean.TRUE) == null) {
323 //System.err.println("Possible jump to " + toHex(t) + " (" + inter.sourceLine(t) + ") from " + toHex(pc) + " (" + inter.sourceLine(pc) + ")");
327 // we just blew it away
328 if(rt == rs) lui_pc[rs] = 0;
333 lui_val[rt] = unsignedImmediate;
338 case 17: // FPU Instructions
340 case 8: // BC1F, BC1T
341 if(jumps.put(new Integer(pc+branchTarget*4+4),Boolean.TRUE) == null) n++;
348 if(printStats) System.err.println("Found " + n + " additional possible branch targets in Text segment");
351 private void findBranchesInData(DataInputStream dis, int size, Hashtable jumps, int textStart, int textEnd) throws IOException {
354 for(int i=0;i<count;i++) {
355 int word = dis.readInt();
356 if((word&3)==0 && word >= textStart && word < textEnd) {
357 if(jumps.put(new Integer(word),Boolean.TRUE) == null) {
358 //System.err.println("Added " + toHex(word) + " as possible branch target (fron data segment)");
364 if(n>0 && printStats) System.err.println("Found " + n + " additional possible branch targets in Data segment");
367 // Helper functions for pretty output
368 final static String toHex(int n) { return "0x" + Long.toString(n & 0xffffffffL, 16); }
369 final static String toHex8(int n) {
370 String s = Long.toString(n & 0xffffffffL, 16);
371 StringBuffer sb = new StringBuffer("0x");
372 for(int i=8-s.length();i>0;i--) sb.append('0');
374 return sb.toString();
377 final static String toOctal3(int n) {
378 char[] buf = new char[3];
379 for(int i=2;i>=0;i--) {
380 buf[i] = (char) ('0' + (n & 7));
383 return new String(buf);
387 private class Option {
388 private java.lang.reflect.Field field;
389 public Option(String name) throws NoSuchFieldException { field = name==null ? null : Compiler.class.getDeclaredField(name); }
390 public void set(Object val) {
391 if(field == null) return;
393 /*field.setAccessible(true); NOT in JDK 1.1 */
394 field.set(Compiler.this,val);
395 } catch(IllegalAccessException e) {
396 System.err.println(e);
399 public Object get() {
400 if(field == null) return null;
402 /*field.setAccessible(true); NOT in JDK 1.1 */
403 return field.get(Compiler.this);
404 } catch(IllegalAccessException e) {
405 System.err.println(e); return null;
408 public Class getType() { return field == null ? null : field.getType(); }
411 private static String[] options = {
412 "fastMem", "Enable fast memory access - RuntimeExceptions will be thrown on faults",
413 "nullPointerCheck", "Enables checking at runtime for null pointer accessses (slows things down a bit, only applicable with fastMem)",
414 "maxInsnPerMethod", "Maximum number of MIPS instructions per java method (128 is optimal with Hotspot)",
415 "pruneCases", "Remove unnecessary case 0xAABCCDD blocks from methods - may break some weird code",
416 "assumeTailCalls", "Assume the JIT optimizes tail calls",
417 "optimizedMemcpy", "Use an optimized java version of memcpy where possible",
418 "debugCompiler", "Output information in the generated code for debugging the compiler - will slow down generated code significantly",
419 "printStats", "Output some useful statistics about the compilation",
420 "runtimeStats", "Keep track of some statistics at runtime in the generated code - will slow down generated code significantly",
421 "supportCall", "Keep a stripped down version of the symbol table in the generated code to support the call() method",
422 "runtimeClass", "Full classname of the Runtime class (default: Runtime) - use this is you put Runtime in a package",
423 "hashClass", "Full classname of a Hashtable class (default: java.util.HashMap) - this must support get() and put()",
424 "unixRuntime", "Use the UnixRuntime (has support for fork, wai, du, pipe, etc)",
425 "pageSize", "The page size (must be a power of two)",
426 "totalPages", "Total number of pages (total mem = pageSize*totalPages, must be a power of two)",
427 "onePage", "One page hack (FIXME: document this better)",
428 "lessConstants", "Use less constants at the cost of speed (FIXME: document this better)",
429 "singleFloat", "Support single precision (32-bit) FP ops only"
432 private Option getOption(String name) {
433 name = name.toLowerCase();
435 for(int i=0;i<options.length;i+=2)
436 if(options[i].toLowerCase().equals(name))
437 return new Option(options[i]);
439 } catch(NoSuchFieldException e) {
444 public void parseOptions(String opts) {
445 if(opts == null || opts.length() == 0) return;
446 StringTokenizer st = new StringTokenizer(opts,",");
447 while(st.hasMoreElements()) {
448 String tok = st.nextToken();
451 if(tok.indexOf("=") != -1) {
452 key = tok.substring(0,tok.indexOf("="));
453 val = tok.substring(tok.indexOf("=")+1);
454 } else if(tok.startsWith("no")) {
455 key = tok.substring(2);
461 Option opt = getOption(key);
463 System.err.println("WARNING: No such option: " + key);
467 if(opt.getType() == String.class)
469 else if(opt.getType() == Integer.TYPE)
471 opt.set(parseInt(val));
472 } catch(NumberFormatException e) {
473 System.err.println("WARNING: " + val + " is not an integer");
475 else if(opt.getType() == Boolean.TYPE)
476 opt.set(new Boolean(val.toLowerCase().equals("true")||val.toLowerCase().equals("yes")));
478 throw new Error("Unknown type: " + opt.getType());
482 private static Integer parseInt(String s) {
485 if(!s.startsWith("0x") && s.endsWith("m")) { s = s.substring(0,s.length()-1); mult = 1024*1024; }
486 else if(!s.startsWith("0x") && s.endsWith("k")) { s = s.substring(0,s.length()-1); mult = 1024; }
488 if(s.length() > 2 && s.startsWith("0x")) n = Integer.parseInt(s.substring(2),16);
489 else n = Integer.parseInt(s);
490 return new Integer(n*mult);
493 private static String wrapAndIndent(String s, int firstindent, int indent, int width) {
494 StringTokenizer st = new StringTokenizer(s," ");
495 StringBuffer sb = new StringBuffer();
496 for(int i=0;i<firstindent;i++)
499 while(st.hasMoreTokens()) {
500 String tok = st.nextToken();
501 if(tok.length() + sofar + 1 > width && sofar > 0) {
503 for(int i=0;i<indent;i++) sb.append(' ');
505 } else if(sofar > 0) {
510 sofar += tok.length();
513 return sb.toString();
516 // This ugliness is to work around a gcj static linking bug (Bug 12908)
517 // The best solution is to force gnu.java.locale.Calendar to be linked in but this'll do
518 static String dateTime() {
520 return new Date().toString();
521 } catch(RuntimeException e) {