Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
BEGIN LICENSE BLOCK ***** Version: CPL 1.0/GPL 2.0/LGPL 2.1 The contents of this file are subject to the Common Public License Version 1.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.eclipse.org/legal/cpl-v10.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Copyright (C) 2006-2008 Charles O Nutter <headius@headius.com> Copyright (C) 2008 Thomas E Enebo <enebo@acm.org> Alternatively, the contents of this file may be used under the terms of either of the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the LGPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of either the GPL or the LGPL, and not to allow others to use your version of this file under the terms of the CPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL or the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the CPL, the GPL or the LGPL. END LICENSE BLOCK ***
 
 package org.jruby.compiler;
 
 
 import java.io.File;
 import java.util.Set;
 import org.jruby.Ruby;
 import  org.objectweb.asm.ClassReader;
 import  org.objectweb.asm.Opcodes;
 import  org.objectweb.asm.util.TraceClassVisitor;
 
 public class JITCompiler implements JITCompilerMBean {
     private static final Logger LOG = LoggerFactory.getLogger("JITCompiler");
     
     public static final boolean USE_CACHE = true;
     public static final String RUBY_JIT_PREFIX = "rubyjit";
 
     public static final String CLASS_METHOD_DELIMITER = "$$";
 
     public static class JITCounts {
         private final AtomicLong compiledCount = new AtomicLong(0);
         private final AtomicLong successCount = new AtomicLong(0);
         private final AtomicLong failCount = new AtomicLong(0);
         private final AtomicLong abandonCount = new AtomicLong(0);
         private final AtomicLong compileTime = new AtomicLong(0);
         private final AtomicLong averageCompileTime = new AtomicLong(0);
         private final AtomicLong codeSize = new AtomicLong(0);
         private final AtomicLong averageCodeSize = new AtomicLong(0);
         private final AtomicLong largestCodeSize = new AtomicLong(0);
     }
 
     private final JITCounts counts = new JITCounts();
     private final ExecutorService executor = new ThreadPoolExecutor(
                     2, // always two threads
                     2,
                     0, // never stop
                     .,
                     new LinkedBlockingQueue<Runnable>(),
                     new DaemonThreadFactory("JRubyJIT"));
    
    public JITCompiler(Ruby ruby) {
        ruby.getBeanManager().register(this);
    }
    public void tryJIT(DefaultMethod methodThreadContext contextString classNameString methodName) {
        if (context.runtime.getInstanceConfig().getCompileMode().shouldJIT()) {
            jitIsEnabled(methodcontextclassNamemethodName);
        }
    }
    public void tearDown() {
        if ( != null) {
            try {
                .shutdown();
            } catch (SecurityException se) {
                // ignore, can't shut down executor
            }
        }
    }
    private void jitIsEnabled(DefaultMethod methodThreadContext contextString classNameString methodName) {
        RubyInstanceConfig instanceConfig = context.runtime.getInstanceConfig();
        
        if (method.incrementCallCount() >= instanceConfig.getJitThreshold()) {
            jitThresholdReached(methodinstanceConfigcontextclassNamemethodName);
        }
    }
    
    private void jitThresholdReached(final DefaultMethod methodfinal RubyInstanceConfig configThreadContext contextfinal String classNamefinal String methodName) {
        // Disable any other jit tasks from entering queue
        method.setCallCount(-1);
        final Ruby runtime = context.runtime;
        
        Runnable jitTask = new Runnable() {
            public void run() {
                try {
                    // The cache is full. Abandon JIT for this method and bail out.
                    ClassCache classCache = config.getClassCache();
                    if (classCache.isFull()) {
                        ..incrementAndGet();
                        return;
                    }
                    // Check if the method has been explicitly excluded
                    if (config.getExcludedMethods().size() > 0) {
                        String excludeModuleName = className;
                        if (method.getImplementationClass().isSingleton()) {
                            IRubyObject possibleRealClass = ((MetaClassmethod.getImplementationClass()).getAttached();
                            if (possibleRealClass instanceof RubyModule) {
                                excludeModuleName = "Meta:" + ((RubyModulepossibleRealClass).getName();
                            }
                        }
                        if ((config.getExcludedMethods().contains(excludeModuleName)
                                || config.getExcludedMethods().contains(excludeModuleName + "#" + methodName)
                                || config.getExcludedMethods().contains(methodName))) {
                            method.setCallCount(-1);
                            log(methodmethodName"skipping method: " + excludeModuleName + "#" + methodName);
                            return;
                        }
                    }
                    String key = SexpMaker.create(methodNamemethod.getArgsNode(), method.getBodyNode());
                    JITClassGenerator generator = new JITClassGenerator(classNamemethodNamekeyruntimemethod);
                    Class<ScriptsourceClass = (Class<Script>) config.getClassCache().cacheClassByKey(keygenerator);
                    if (sourceClass == null) {
                        // class could not be found nor generated; give up on JIT and bail out
                        ..incrementAndGet();
                        return;
                    }
                    // successfully got back a jitted method
                    ..incrementAndGet();
                    // finally, grab the script
                    Script jitCompiledScript = sourceClass.newInstance();
                    // add to the jitted methods set
                    Set<ScriptjittedMethods = runtime.getJittedMethods();
                    jittedMethods.add(jitCompiledScript);
                    // logEvery n methods based on configuration
                    if (config.getJitLogEvery() > 0) {
                        int methodCount = jittedMethods.size();
                        if (methodCount % config.getJitLogEvery() == 0) {
                            log(methodmethodName"live compiled methods: " + methodCount);
                        }
                    }
                    if (config.isJitLogging()) {
                        log(methodclassName + "." + methodName"done jitting");
                    }
                    method.switchToJitted(jitCompiledScriptgenerator.callConfig());
                    return;
                } catch (Throwable t) {
                    if (runtime.getDebug().isTrue()) {
                        t.printStackTrace();
                    }
                    if (config.isJitLoggingVerbose()) {
                        log(methodclassName + "." + methodName"could not compile"t.getMessage());
                    }
                    ..incrementAndGet();
                    return;
                }
            }
        };
        // if background JIT is enabled and threshold is > 0 and we have an executor...
        if (config.getJitBackground() &&
                config.getJitThreshold() > 0 &&
                 != null) {
            // JIT in background
            try {
                .submit(jitTask);
            } catch (RejectedExecutionException ree) {
                // failed to submit, just run it directly
                jitTask.run();
            }
        } else {
            // just run directly
            jitTask.run();
        }
    }
    public static String getHashForString(String str) {
        return getHashForBytes(RubyEncoding.encodeUTF8(str));
    }
    public static String getHashForBytes(byte[] bytes) {
        try {
            MessageDigest sha1 = MessageDigest.getInstance("SHA1");
            sha1.update(bytes);
            byte[] digest = sha1.digest();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < digest.lengthi++) {
                builder.append(Integer.toString( ( digest[i] & 0xff ) + 0x100, 16).substring( 1 ));
            }
            return builder.toString().toUpperCase(.);
        } catch (NoSuchAlgorithmException nsae) {
            throw new RuntimeException(nsae);
        }
    }
    public static void saveToCodeCache(Ruby rubybyte[] bytecodeString packageNameFile cachedClassFile) {
        String codeCache = .;
        File codeCacheDir = new File(codeCache);
        if (!codeCacheDir.exists()) {
            ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " does not exist");
        } else if (!codeCacheDir.isDirectory()) {
            ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " is not a directory");
        } else if (!codeCacheDir.canWrite()) {
            ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " is not writable");
        } else {
            if (!new File(codeCachepackageName).isDirectory()) {
                boolean createdDirs = new File(codeCachepackageName).mkdirs();
                if (!createdDirs) {
                    ruby.getWarnings().warn("could not create JIT cache dir: " + new File(codeCachepackageName));
                }
            }
            // write to code cache
            FileOutputStream fos = null;
            try {
                if (..info("writing jitted code to to " + cachedClassFile);
                fos = new FileOutputStream(cachedClassFile);
                fos.write(bytecode);
            } catch (Exception e) {
                e.printStackTrace();
                // ignore
            } finally {
                try {fos.close();} catch (Exception e) {}
            }
        }
    }
    
    public static class JITClassGenerator implements ClassCache.ClassGenerator {
        public JITClassGenerator(String classNameString methodNameString keyRuby rubyDefaultMethod methodJITCounts counts) {
            this. = .;
            if (. == Opcodes.V1_7) {
                // recent Java 7 seems to have a bug that leaks definitions across cousin classloaders
                // so we force the class name to be unique to this runtime
                 = getHashForString(key) + Math.abs(ruby.hashCode());
            } else {
                 = getHashForString(key);
            }
            this. =  + "/" + className.replace('.''/') +  + JavaNameMangler.mangleMethodName(methodName) + "_" + ;
            this. = this..replaceAll("/"".");
            this. = method.getBodyNode();
            this. = method.getArgsNode();
            this. = methodName;
             = calculateFilename();
             = method.getStaticScope();
             = new StandardASMCompiler(this.);
            this. = ruby;
            this. = counts;
        }
        
        @SuppressWarnings("unchecked")
        protected void compile() {
            if ( != nullreturn;
            
            // check if we have a cached compiled version on disk
            String codeCache = .;
            File cachedClassFile = new File(codeCache + "/" +  + ".class");
            if (codeCache != null &&
                    cachedClassFile.exists()) {
                FileInputStream fis = null;
                try {
                    if (..info("loading cached code from: " + cachedClassFile);
                    fis = new FileInputStream(cachedClassFile);
                     = new byte[(int)fis.getChannel().size()];
                    fis.read();
                     = new ClassReader().getClassName();
                    return;
                } catch (Exception e) {
                    // ignore and proceed to compile
                } finally {
                    try {fis.close();} catch (Exception e) {}
                }
            }
            
            // Time the compilation
            long start = System.nanoTime();
            .startScript();
            final ASTCompiler compiler = .getInstanceConfig().newCompiler();
            CompilerCallback args = new CompilerCallback() {
                public void call(BodyCompiler context) {
                    compiler.compileArgs(contexttrue);
                }
            };
            ASTInspector inspector = new ASTInspector();
            if (.getInstanceConfig().isJitDumping()) {
                inspector = new ASTInspector(true);
            }
            // check args first, since body inspection can depend on args
            inspector.inspect();
            inspector.inspect();
            BodyCompiler methodCompiler;
            if ( != null) {
                // we have a body, do a full-on method
                methodCompiler = .startFileMethod(argsinspector);
                compiler.compileBody(methodCompiler,true);
            } else {
                // If we don't have a body, check for required or opt args
                // if opt args, they could have side effects
                // if required args, need to raise errors if too few args passed
                // otherwise, method does nothing, make it a nop
                if ( != null && (.getRequiredArgsCount() > 0 || .getOptionalArgsCount() > 0)) {
                    methodCompiler = .startFileMethod(argsinspector);
                    methodCompiler.loadNil();
                } else {
                    methodCompiler = .startFileMethod(nullinspector);
                    methodCompiler.loadNil();
                     = .;
                }
            }
            methodCompiler.endBody();
            .endScript(falsefalse);
            
            // if we haven't already decided on a do-nothing call
            if ( == null) {
                 = inspector.getCallConfig();
            }
            
             = .getClassByteArray();
            if (.getInstanceConfig().isJitDumping()) {
                TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(.));
                new ClassReader().accept(tcv, 0);
            }
            
            if (. > .getInstanceConfig().getJitMaxSize()) {
                 = null;
                throw new NotCompilableException(
                        "JITed method size exceeds configured max of " +
                        .getInstanceConfig().getJitMaxSize());
            }
            if (codeCache != null) {
                JITCompiler.saveToCodeCache(cachedClassFile);
            }
            
            ..incrementAndGet();
            ..addAndGet(System.nanoTime() - start);
            ..addAndGet(.);
            synchronized () {
                if (..get() < .) {
                    ..set(.);
                }
            }
        }
        public void generate() {
            compile();
        }
        
        public byte[] bytecode() {
            return ;
        }
        public String name() {
            return ;
        }
        
        public CallConfiguration callConfig() {
            compile();
            return ;
        }
        @Override
        public String toString() {
            return  + "() at " + .getPosition().getFile() + ":" + .getPosition().getLine();
        }
        
        private final StandardASMCompiler asmCompiler;
        private final StaticScope staticScope;
        private final Node bodyNode;
        private final ArgsNode argsNode;
        private final Ruby ruby;
        private final String packageName;
        private final String className;
        private final String filename;
        private final String methodName;
        private final JITCounts counts;
        private final String digestString;
        private CallConfiguration jitCallConfig;
        private byte[] bytecode;
        private String name;
    }
    
    private static String calculateFilename(ArgsNode argsNodeNode bodyNode) {
        if (bodyNode != nullreturn bodyNode.getPosition().getFile();
        if (argsNode != nullreturn argsNode.getPosition().getFile();
        
        return "__eval__";
    }
    static void log(DefaultMethod methodString nameString messageString... reason) {
        String className = method.getImplementationClass().getBaseName();
        
        if (className == nullclassName = "<anon class>";
        StringBuilder builder = new StringBuilder(message + ":" + className + "." + name + " at " + method.getPosition());
        
        if (reason.length > 0) {
            builder.append(" because of: \"");
            for (int i = 0; i < reason.lengthi++) {
                builder.append(reason[i]);
            }
            builder.append('"');
        }
        
        .info(builder.toString());
    }
    public long getSuccessCount() {
        return ..get();
    }
    public long getCompileCount() {
        return ..get();
    }
    public long getFailCount() {
        return ..get();
    }
    public long getCompileTime() {
        return ..get() / 1000;
    }
    public long getAbandonCount() {
        return ..get();
    }
    
    public long getCodeSize() {
        return ..get();
    }
    
    public long getAverageCodeSize() {
        return ..get();
    }
    
    public long getAverageCompileTime() {
        return ..get() / 1000;
    }
    
    public long getLargestCodeSize() {
        return ..get();
    }
New to GrepCode? Check out our FAQ X