/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 by Trifork * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package erjang.beam; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import kilim.analysis.ClassInfo; import kilim.analysis.ClassWeaver; import kilim.mirrors.Detector; import kilim.mirrors.ClassMirrorNotFoundException; import kilim.mirrors.Mirrors; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.util.CheckClassAdapter; import com.ericsson.otp.erlang.OtpAuthException; import erjang.EBinary; import erjang.EFun; import erjang.EObject; import erjang.ERT; import erjang.ETuple; import erjang.beam.analysis.BeamTypeAnalysis; import erjang.ErjangCodeCache; import erjang.beam.loader.ErjangBeamDisLoader; public class Compiler implements Opcodes { private ClassRepo classRepo; /** * @param repo * @throws IOException * @throws OtpAuthException * */ public Compiler(ClassRepo repo) throws OtpAuthException, IOException { this.classRepo = repo; } public static void compile(BeamFileData data, ClassRepo repo) throws IOException { // reset thread-local data ClassWeaver.reset(); // class writer, phase 4 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); // class checker, optional phase //CheckClassAdapter ca = new CheckClassAdapter(cw); // the java bytecode generator, phase 3 CompilerVisitor cv = new CompilerVisitor(cw, repo); // the type analysis, phase 2 BeamTypeAnalysis analysis = new BeamTypeAnalysis(cv); // the module analyzer, phase 1 (not chained to phase 2) ModuleAnalyzer ma = new ModuleAnalyzer(); data.accept(ma); cv.setFunInfos(ma.getFunInfos()); try { // go! data.accept(analysis); } catch (Error e) { e.printStackTrace(); } byte[] byteArray = cw.toByteArray(); // to emit pre-kilim code [for debugging] repo.store("raw/"+cv.getInternalClassName(), byteArray); boolean written = false; ClassWeaver cwe = new ClassWeaver(byteArray, new ErjangDetector( cv.getInternalClassName(), cv.non_pausable_methods)); cwe.weave(); for (ClassInfo ci : cwe.getClassInfos()) { String name = ci.className; byte[] bytes = ci.bytes; String iname = name.replace('.', '/'); if (iname.equals(cv.getInternalClassName())) { written = true; } repo.store(iname, bytes); } if (!written) { // no pausable functions in module! repo.store(cv.getInternalClassName(), byteArray); } } public void compile(File file, BeamLoader beam_parser) throws IOException { EBinary eb = EUtil.readFile(file); BeamFileData bfd = beam_parser.load(eb.getByteArray()); compile(bfd, this.classRepo); } static public class ErjangDetector extends Detector { private final String className; private final Set<String> nonPausableMethods; /** * @param className * @param nonPausableMethods * @param mirrors */ public ErjangDetector(String className, Set<String> nonPausableMethods) { super(Detector.DEFAULT.mirrors); this.className = className; this.nonPausableMethods = nonPausableMethods; } static Pattern FUN = Pattern.compile("^erjang\\.m\\..*\\$FN_.*__([0-9])+$"); /* (non-Javadoc) * @see kilim.analysis.Detector#getSuperClasses(java.lang.String) */ @Override public ArrayList<String> getSuperClasses(String cc) throws ClassMirrorNotFoundException { Matcher m = FUN.matcher(cc); if (m.matches()) { int arity = Integer.parseInt(m.group(1)); ArrayList<String> result = new ArrayList<String>(); result.add(EFun.class.getName()+arity); result.add(EFun.class.getName()); result.add(EObject.class.getName()); result.add(Object.class.getName()); return result; } return super.getSuperClasses(cc); } @Override public int getPausableStatus(String className, String methodName, String desc) { // System.out.println("status? "+className+"#"+methodName+""+desc); if (className.startsWith(CompilerVisitor.ETUPLE_NAME)) { return Detector.METHOD_NOT_PAUSABLE; } if (className.startsWith(CompilerVisitor.EFUN_NAME)) { if (methodName.equals("go")) return Detector.PAUSABLE_METHOD_FOUND; if (methodName.equals("invoke")) return Detector.PAUSABLE_METHOD_FOUND; return Detector.METHOD_NOT_PAUSABLE; } if (className.equals(this.className)) { if (methodName.endsWith("$tail")) return Detector.METHOD_NOT_PAUSABLE; if (methodName.endsWith("init>")) return Detector.METHOD_NOT_PAUSABLE; if (methodName.equals("module_name")) return Detector.METHOD_NOT_PAUSABLE; if (nonPausableMethods.contains(methodName)) return Detector.METHOD_NOT_PAUSABLE; return Detector.PAUSABLE_METHOD_FOUND; } return super.getPausableStatus(className, methodName, desc); } } public static String moduleClassName(String moduleName) { String cn = EUtil.toJavaIdentifier(moduleName); String base = "erjang/m/" + cn + "/" + cn; return base; } public static void main(String[] args) throws Exception { File out_dir = new File("target/compiled"); out_dir.mkdirs(); BeamLoader beamParser = new ErjangBeamDisLoader(); for (int i = 0; i < args.length; i++) { if (args[i].endsWith(".beam")) { File in = new File(args[i]); if (!in.exists() || !in.isFile() || !in.canRead()) throw new IOException("bad permissions for " + in); int idx = args[i].lastIndexOf('.'); int idx0 = args[i].lastIndexOf(File.separator); String shortName = args[i].substring(idx0 + 1, idx); File out = new File(out_dir, ErjangCodeCache.moduleJarFileName(shortName, crcFile(in))); // TODO: don't use the code cache location. JarClassRepo jcp = new JarClassRepo(out); System.out.println("compiling " + in + " -> " + out + " ..."); new Compiler(jcp).compile(in, beamParser); jcp.close(); } } } private static long crcFile(File file) throws IOException { CheckedInputStream cis = null; long fileSize = 0; cis = new CheckedInputStream(new FileInputStream(file), new CRC32()); try { byte[] buf = new byte[4 * 1024]; while (cis.read(buf) >= 0) ; return cis.getChecksum().getValue(); } finally { cis.close(); } } }