PageRenderTime 125ms CodeModel.GetById 60ms app.highlight 24ms RepoModel.GetById 36ms app.codeStats 0ms

/rpython/translator/jvm/genjvm.py

https://bitbucket.org/kcr/pypy
Python | 303 lines | 276 code | 12 blank | 15 comment | 0 complexity | e575e544177f1088454b992ffc7b8351 MD5 | raw file
  1"""
  2Backend for the JVM.
  3"""
  4
  5from __future__ import with_statement
  6import os
  7import re
  8import subprocess
  9import sys
 10
 11import py
 12from rpython.rtyper.ootypesystem import ootype
 13from rpython.tool.udir import udir
 14from rpython.translator.backendopt.all import backend_optimizations
 15from rpython.translator.backendopt.checkvirtual import check_virtual_methods
 16from rpython.translator.oosupport.genoo import GenOO
 17from rpython.translator.translator import TranslationContext
 18
 19from rpython.translator.jvm.constant import (
 20    JVMConstantGenerator, JVMStaticMethodConst, JVMCustomDictConst,
 21    JVMWeakRefConst)
 22from rpython.translator.jvm.database import Database
 23from rpython.translator.jvm.generator import JasminGenerator
 24from rpython.translator.jvm.log import log
 25from rpython.translator.jvm.node import EntryPoint, Function
 26from rpython.translator.jvm.opcodes import opcodes
 27from rpython.translator.jvm.option import getoption
 28from rpython.translator.jvm.prebuiltnodes import create_interlink_node
 29
 30MIN_JAVA_VERSION = '1.6.0'
 31
 32class JvmError(Exception):
 33    """ Indicates an error occurred in JVM backend """
 34
 35    def pretty_print(self):
 36        print str(self)
 37    pass
 38
 39class JvmSubprogramError(JvmError):
 40    """ Indicates an error occurred running some program """
 41    def __init__(self, res, args, stdout, stderr):
 42        self.res = res
 43        self.args = args
 44        self.stdout = stdout
 45        self.stderr = stderr
 46
 47    def __str__(self):
 48        return "Error code %d running %s" % (self.res, repr(self.args))
 49
 50    def pretty_print(self):
 51        JvmError.pretty_print(self)
 52        print "vvv Stdout vvv\n"
 53        print self.stdout
 54        print "vvv Stderr vvv\n"
 55        print self.stderr
 56
 57
 58class JvmGeneratedSource(object):
 59
 60    """
 61    An object which represents the generated sources. Contains methods
 62    to find out where they are located, to compile them, and to execute
 63    them.
 64
 65    For those interested in the location of the files, the following
 66    attributes exist:
 67    tmpdir --- root directory from which all files can be found (py.path obj)
 68    javadir --- the directory containing *.java (py.path obj)
 69    classdir --- the directory where *.class will be generated (py.path obj)
 70    package --- a string with the name of the package (i.e., 'java.util')
 71
 72    The following attributes also exist to find the state of the sources:
 73    compiled --- True once the sources have been compiled successfully
 74    """
 75    _cached = None
 76
 77    def __init__(self, tmpdir, package):
 78        """
 79        'tmpdir' --- the base directory where the sources are located
 80        'package' --- the package the sources are in; if package is pypy.jvm,
 81        then we expect to find the sources in $tmpdir/pypy/jvm
 82        'jfiles' --- list of files we need to run jasmin on
 83        """
 84        self.tmpdir = tmpdir
 85        self.package = package
 86        self.compiled = False
 87        self.jasmin_files = None
 88
 89        # Determine various paths:
 90        self.thisdir = py.path.local(__file__).dirpath()
 91        self.rootdir = self.thisdir.join('src')
 92        self.srcdir = self.rootdir.join('pypy')
 93        self.jnajar = self.rootdir.join('jna.jar')
 94        self.jasminjar = self.rootdir.join('jasmin.jar')
 95
 96        # Compute directory where .j files are
 97        self.javadir = self.tmpdir
 98        for subpkg in package.split('.'):
 99            self.javadir = self.javadir.join(subpkg)
100
101        # Compute directory where .class files should go
102        self.classdir = self.javadir
103
104    def set_jasmin_files(self, jfiles):
105        self.jasmin_files = jfiles
106
107    def _invoke(self, args, allow_stderr):
108        import sys
109        if sys.platform == 'nt':
110            shell = True
111        else:
112            shell = False
113        subp = subprocess.Popen(
114            args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
115            shell=shell, universal_newlines=True)
116        stdout, stderr = subp.communicate()
117        res = subp.wait()
118        if res or (not allow_stderr and stderr):
119            raise JvmSubprogramError(res, args, stdout, stderr)
120        return stdout, stderr, res
121
122    def _compile_helper(self):
123        # HACK: compile the Java helper classes.  Should eventually
124        # use rte.py
125        if JvmGeneratedSource._cached == self.classdir:
126           return
127        log.red('Compiling java classes')
128        javafiles = self.srcdir.listdir('*.java')
129        javasrcs = [str(jf) for jf in javafiles]
130        self._invoke([getoption('javac'),
131                      '-nowarn',
132                      '-d', str(self.classdir),
133                      '-classpath', str(self.jnajar),
134                      ] + javasrcs,
135                     True)
136        # NOTE if you are trying to add more caching: some .java files
137        # compile to several .class files of various names.
138        JvmGeneratedSource._cached = self.classdir
139
140    def compile(self):
141        """
142        Compiles the .java sources into .class files, ready for execution.
143        """
144        jascmd = [
145            getoption('java'),
146            '-Djava.awt.headless=true',
147            '-jar', str(self.jasminjar),
148            '-g',
149            '-d',
150            str(self.javadir)]
151
152        def split_list(files):
153            "Split the files list into manageable pieces"
154
155            # - On Windows 2000, commands in .bat are limited to 2047 chars.
156            # - But the 'jasmin' script contains a line like
157            #     path_to_jre/java -jar path_to_jasmin/jasmin.jar $*
158            # So we limit the length of arguments files to:
159            MAXLINE = 1500
160
161            chunk = []
162            chunklen = 0
163            for f in files:
164                # Account for the space between items
165                chunklen += len(f) + 1
166                if chunklen > MAXLINE:
167                    yield chunk
168                    chunk = []
169                    chunklen = len(f)
170                chunk.append(f)
171            if chunk:
172                yield chunk
173
174        for files in split_list(self.jasmin_files):
175            #print "Invoking jasmin on %s" % files
176            self._invoke(jascmd + files, False)
177            #print "... completed!"
178
179        self.compiled = True
180        self._compile_helper()
181
182    def _make_str(self, a):
183        if isinstance(a, ootype._string):
184            return a._str
185        return str(a)
186
187    def execute(self, args):
188        """
189        Executes the compiled sources in a separate process.  Returns the
190        output as a string.  The 'args' are provided as arguments,
191        and will be converted to strings.
192        """
193        assert self.compiled
194        strargs = [self._make_str(a) for a in args]
195        cmd = [getoption('java'),
196               '-Xmx256M', # increase the heapsize so the microbenchmarks run
197               '-Djava.awt.headless=true',
198               '-cp',
199               str(self.javadir)+os.pathsep+str(self.jnajar),
200               self.package+".Main"] + strargs
201        print "Invoking java to run the code"
202        stdout, stderr, retval = self._invoke(cmd, True)
203        print "...done!"
204        sys.stderr.write(stderr)
205        return stdout, stderr, retval
206
207def generate_source_for_function(func, annotation, backendopt=False):
208
209    """
210    Given a Python function and some hints about its argument types,
211    generates JVM sources that call it and print the result.  Returns
212    the JvmGeneratedSource object.
213    """
214
215    if hasattr(func, 'im_func'):
216        func = func.im_func
217    t = TranslationContext()
218    ann = t.buildannotator()
219    ann.build_types(func, annotation)
220    t.buildrtyper(type_system="ootype").specialize()
221    if backendopt:
222        check_virtual_methods(ootype.ROOT)
223        backend_optimizations(t)
224    main_graph = t.graphs[0]
225    if getoption('view'): t.view()
226    if getoption('wd'): tmpdir = py.path.local('.')
227    else: tmpdir = udir
228    jvm = GenJvm(tmpdir, t, EntryPoint(main_graph, True, True))
229    return jvm.generate_source()
230
231_missing_support_programs = None
232
233def detect_missing_support_programs():
234    global _missing_support_programs
235    if _missing_support_programs is not None:
236        if _missing_support_programs:
237            py.test.skip(_missing_support_programs)
238        return
239
240    def missing(msg):
241        global _missing_support_programs
242        _missing_support_programs = msg
243        py.test.skip(msg)
244
245    for cmd in 'javac', 'java':
246        if py.path.local.sysfind(getoption(cmd)) is None:
247            missing("%s is not on your path" % cmd)
248    if not _check_java_version(MIN_JAVA_VERSION):
249        missing('Minimum of Java %s required' % MIN_JAVA_VERSION)
250    _missing_support_programs = False
251
252def _check_java_version(version):
253    """Determine if java meets the specified version"""
254    cmd = [getoption('java'), '-version']
255    with open(os.devnull, 'w') as devnull:
256        stderr = subprocess.Popen(cmd, stdout=devnull,
257                                  stderr=subprocess.PIPE).communicate()[1]
258    search = re.search('[\.0-9]+', stderr)
259    return search and search.group() >= version
260
261class GenJvm(GenOO):
262
263    """ Master object which guides the JVM backend along.  To use,
264    create with appropriate parameters and then invoke
265    generate_source().  *You can not use one of these objects more than
266    once.* """
267
268    TypeSystem = lambda X, db: db # TypeSystem and Database are the same object
269    Function = Function
270    Database = Database
271    opcodes = opcodes
272    log = log
273
274    ConstantGenerator = JVMConstantGenerator
275    CustomDictConst   = JVMCustomDictConst
276    StaticMethodConst = JVMStaticMethodConst
277    WeakRefConst = JVMWeakRefConst
278
279    def __init__(self, tmpdir, translator, entrypoint):
280        """
281        'tmpdir' --- where the generated files will go.  In fact, we will
282        put our binaries into the directory pypy/jvm
283        'translator' --- a TranslationContext object
284        'entrypoint' --- if supplied, an object with a render method
285        """
286        GenOO.__init__(self, tmpdir, translator, entrypoint)
287        self.jvmsrc = JvmGeneratedSource(tmpdir, getoption('package'))
288
289    def append_prebuilt_nodes(self):
290        create_interlink_node(self.db)
291
292    def generate_source(self):
293        """ Creates the sources, and returns a JvmGeneratedSource object
294        for manipulating them """
295        GenOO.generate_source(self)
296        self.jvmsrc.set_jasmin_files(self.db.jasmin_files())
297        return self.jvmsrc
298
299    def create_assembler(self):
300        """ Creates and returns a Generator object according to the
301        configuration.  Right now, however, there is only one kind of
302        generator: JasminGenerator """
303        return JasminGenerator(self.db, self.jvmsrc.javadir)