Re: [pypy-dev] [pypy-svn] r33528 - in pypy/dist/pypy/translator: jvm jvm/src jvm/test oosupport
niko@codespeak.net wrote:
Author: niko Date: Sun Oct 22 01:36:59 2006 New Revision: 33528
Added: pypy/dist/pypy/translator/jvm/__init__.py pypy/dist/pypy/translator/jvm/option.py pypy/dist/pypy/translator/jvm/test/ pypy/dist/pypy/translator/jvm/test/__init__.py pypy/dist/pypy/translator/jvm/test/runtest.py pypy/dist/pypy/translator/jvm/test/test_bool.py pypy/dist/pypy/translator/jvm/typesystem.py Removed: pypy/dist/pypy/translator/jvm/types.py Modified: pypy/dist/pypy/translator/jvm/ (props changed) pypy/dist/pypy/translator/jvm/conftest.py pypy/dist/pypy/translator/jvm/database.py pypy/dist/pypy/translator/jvm/generator.py pypy/dist/pypy/translator/jvm/genjvm.py pypy/dist/pypy/translator/jvm/node.py pypy/dist/pypy/translator/jvm/opcodes.py pypy/dist/pypy/translator/jvm/src/PyPy.java pypy/dist/pypy/translator/oosupport/metavm.py Log: further work towards working jvm tests --- this is an intermediate check-in, still evolving the JVM TypeSystem and database.
Added: pypy/dist/pypy/translator/jvm/__init__.py ==============================================================================
Modified: pypy/dist/pypy/translator/jvm/conftest.py ============================================================================== --- pypy/dist/pypy/translator/jvm/conftest.py (original) +++ pypy/dist/pypy/translator/jvm/conftest.py Sun Oct 22 01:36:59 2006 @@ -13,6 +13,10 @@ ## help='View the graphs before they are generated'), Option('--wd', action='store_true', dest='wd', default=False, help='Output to current directory instead of /tmp'), + Option('--noassemble', action='store_true', dest="noasm", default=False, + help="don't assemble jasmin files"), + Option('--norun', action='store_true', dest="norun", default=False, + help="don't run the compiled executable"), Option('--package', action='store', dest='package', default='pypy', help='Package to output generated classes into') #Option('--opt', action='XXX', dest='YYY', default=DEF, help='HELP')
Modified: pypy/dist/pypy/translator/jvm/database.py ============================================================================== --- pypy/dist/pypy/translator/jvm/database.py (original) +++ pypy/dist/pypy/translator/jvm/database.py Sun Oct 22 01:36:59 2006 @@ -1,11 +1,85 @@ """ -For now, a simple worklist abstraction. +The database tracks which graphs have already been generated, and maintains +a worklist. It also contains a pointer to the type system. It is passed +into every node for generation along with the generator. """ +from cStringIO import StringIO +from pypy.rpython.ootypesystem import ootype +from pypy.translator.jvm.typesystem import jvm_method_desc +from pypy.translator.jvm import node +import pypy.translator.jvm.generator as jvmgen +import pypy.translator.jvm.typesystem as jvmtypes
class Database: - def __init__(self): - self._pending = [] + def __init__(self, ts): + # Public attributes: + self.type_system = ts + + # Private attributes: + self._classes = {} # Maps ootype class objects to node.Class objects + self._counter = 0 # Used to create unique names + self._pending = [] # Worklist + + def _make_unique_name(self, nm): + cnt = self._counter + self._counter += 1 + return nm + "_" + str(cnt) + "_" + + def get_class_for(self, ooclass): + """ Given an OOTypeSystem Instance object representing a user + defined class (ooclass), returns a node.Class object representing + its jvm counterpart. """ + + # Create class object if it does not already exist: + if ooclass in self._classes: + return self._classes[ooclass] + clname = self._make_unique_name(ooclass._name) + clobj = self._classes[ooclass] = node.Class(clname) + + # Add fields: + for fieldnm, (fieldty, fielddef) in ooclass._fields.iteritems(): + if ftype is ootype.Void: continue + fieldnm = self._make_unique_name(fieldnm) + fieldty = self.type_system.ootype_to_jvm(ftype) + clobj.add_field(fieldty, fieldnm) # TODO --- fielddef?? + + # Add methods: + for mname, mimpl in ooclass._methods.iteritems(): + if not hasattr(mimpl, 'graph'): + # Abstract method + TODO + else: + # if the first argument's type is not a supertype of + # this class it means that this method this method is + # not really used by the class: don't render it, else + # there would be a type mismatch. + args = m_meth.graph.getargs() + SELF = args[0].concretetype + if not ootype.isSubclass(ooclass, SELF): continue + mobj = _method_for_graph(clobj, False, mimpl.graph) + clobj.add_method(mobj) + + return clobj + + def _method_for_graph(self, classobj, is_static, graph):
+ """ + Creates a node.Function object for a particular graph. Adds the + method to 'classobj', which should be a node.Class object. + """ + + # Build up a func object + func_name = self._make_unique_name(graph.name) + argtypes = [arg.concretetype for arg in graph.getargs() + if arg.concretetype is not ootype.Void] + jargtypes = [self.type_system.ootype_to_jvm(argty) + for argty in argtypes] + rettype = graph.getreturnvar().concretetype + jrettype = self.type_system.ootype_to_jvm(rettype) + funcobj = self._translated[cachekey] = node.Function( + classobj, func_name, jargtypes, jrettype, graph, is_static) + return funcobj + def pending_node(self, node): self._pending.append(node)
@@ -14,6 +88,3 @@
def pop(self): return self._pending.pop() - - def method_for_graph(self, graph): -
Modified: pypy/dist/pypy/translator/jvm/generator.py ============================================================================== --- pypy/dist/pypy/translator/jvm/generator.py (original) +++ pypy/dist/pypy/translator/jvm/generator.py Sun Oct 22 01:36:59 2006 @@ -1,6 +1,7 @@ +import os # +from pypy.objspace.flow import model as flowmodel from pypy.translator.oosupport.metavm import Generator - -__all__ = ['JasminGenerator'] +from pypy.translator.jvm.typesystem import JvmType
# ___________________________________________________________________________ # JVM Opcode Flags: @@ -8,11 +9,11 @@ # Indicates certain properties of each opcode. Used mainly for debugging # assertions
-NOFLAGS = 0 -BRANCH = 1 # Opcode is a branching opcode (implies a label argument) -INTARG = 2 # Opcode has an integer argument -CONST = 6 # Opcode has specialized variants (implies INTARG) -INVOKE = 8 # Opcode is some kind of method invocation +NOFLAGS = 0 +BRANCH = 1 # Opcode is a branching opcode (implies a label argument) +INTARG = 2 # Opcode has an integer argument +CONSTSPEC = 6 # Opcode has specialized variants (implies INTARG) +INVOKE = 8 # Opcode is some kind of method invocation
# ___________________________________________________________________________ # JVM Opcodes: @@ -27,6 +28,48 @@ """ self.flags = flags self.jvmstr = jvmstr + +class OpcodeFamily(object): + """ + Many opcodes in JVM have variants that depend on the type of the + operands; for example, one must choose the correct ALOAD, ILOAD, + or DLOAD depending on whether one is loading a reference, integer, + or double variable respectively. Each instance of this class + defines one 'family' of opcodes, such as the LOAD family shown + above, and produces Opcode objects specific to a particular type. + """ + def __init__(self, flags, suffix): + """ + flags is a set of flags (see above) that describe opcode + jvmstr is the name for jasmin printouts + """ + self.flags = flags + self.suffix = suffix + self.cache = {} + + def _o(self, prefix): + try: + return self.cache[prefix] + except KeyError: + self.cache[prefix] = obj = Opcode(self.flags, prefix+self.suffix) + return obj + + def for_type(self, argtype): + """ Returns a customized opcode of this family appropriate to + 'argtype', a JvmType object. """ + + # These are always true: + if self[0] == 'L': return self._o("A") # Objects + if self[0] == '[': return self._o("A") # Arrays + if self == 'I': return self._o("I") # Integers + if self == 'J': return self._o("L") # Integers + if self == 'D': return self._o("D") # Doubles + if self == 'C': return self._o("C") # Characters + if self == 'B': return self._o("B") # Bytes + if self == 'V': return self._o("") # Void [used by RETURN] + + # TODO --- extend? etc + unimplemented
# Define the opcodes for IFNE, IFEQ, IFLT, IF_ICMPLT, etc. The IFxx # variants compare a single integer arg against 0, and the IF_ICMPxx @@ -48,11 +91,11 @@
# Other opcodes GOTO = Opcode(BRANCH, 'goto') -ICONST = Opcode(CONST, 'iconst') -DCONST_0 = Opcode(CONST, 'dconst_0') -DCONST_1 = Opcode(CONST, 'dconst_1') -LCONST_0 = Opcode(CONST, 'lconst_0') -LCONST_1 = Opcode(CONST, 'lconst_1') +ICONST = Opcode(CONSTSPEC, 'iconst') +DCONST_0 = Opcode(NOFLAGS, 'dconst_0') +DCONST_1 = Opcode(NOFLAGS, 'dconst_1') +LCONST_0 = Opcode(NOFLAGS, 'lconst_0') +LCONST_1 = Opcode(NOFLAGS, 'lconst_1') GETFIELD = Opcode(NOFLAGS, 'getfield') PUTFIELD = Opcode(NOFLAGS, 'putfield') GETSTATIC = Opcode(NOFLAGS, 'getstatic') @@ -66,16 +109,56 @@ IDIV = Opcode(NOFLAGS, 'idiv') IREM = Opcode(NOFLAGS, 'irem') IAND = Opcode(NOFLAGS, 'iand') +IOR = Opcode(NOFLAGS, 'ior') ISHL = Opcode(NOFLAGS, 'ishl') ISHR = Opcode(NOFLAGS, 'ishr') +IUSHR = Opcode(NOFLAGS, 'iushr') DCMPG = Opcode(NOFLAGS, 'dcmpg') DCMPL = Opcode(NOFLAGS, 'dcmpl') NOP = Opcode(NOFLAGS, 'nop') I2D = Opcode(NOFLAGS, 'i2d') I2L = Opcode(NOFLAGS, 'i2l') +D2I= Opcode(NOFLAGS, 'd2i') +L2I = Opcode(NOFLAGS, 'l2i') +ATHROW = Opcode(NOFLAGS, 'athrow') +DNEG = Opcode(NOFLAGS, 'dneg') +DADD = Opcode(NOFLAGS, 'dadd') +DSUB = Opcode(NOFLAGS, 'dsub') +DMUL = Opcode(NOFLAGS, 'dmul') +DDIV = Opcode(NOFLAGS, 'ddiv') +DREM = Opcode(NOFLAGS, 'drem') +LNEG = Opcode(NOFLAGS, 'lneg') +LADD = Opcode(NOFLAGS, 'ladd') +LSUB = Opcode(NOFLAGS, 'lsub') +LMUL = Opcode(NOFLAGS, 'lmul') +LDIV = Opcode(NOFLAGS, 'ldiv') +LREM = Opcode(NOFLAGS, 'lrem') +LAND = Opcode(NOFLAGS, 'land') +LOR = Opcode(NOFLAGS, 'lor') +LXOR = Opcode(NOFLAGS, 'lxor') +LSHL = Opcode(NOFLAGS, 'lshl') +LSHR = Opcode(NOFLAGS, 'lshr') +LUSHR = Opcode(NOFLAGS, 'lushr') + +# Loading/storing local variables +LOAD = OpcodeFamily(INTARG, "load") +STORE = OpcodeFamily(INTARG, "store") +RETURN = OpcodeFamily(NOFLAGS, "return") + +# Loading/storing from arrays +# *NOTE*: This family is characterized by the type of the ELEMENT, +# not the type of the ARRAY. +# +# Also: here I break from convention by naming the objects ARRLOAD +# rather than ALOAD, even though the suffix is 'aload'. This is to +# avoid confusion with the ALOAD opcode. +ARRLOAD = OpcodeFamily(NOFLAGS, "aload") +ARRSTORE = OpcodeFamily(NOFLAGS, "astore")
# ___________________________________________________________________________ # Helper Method Information +# +# These are used by code outside of this module as well.
class Method(object): def __init__(self, classnm, methnm, desc, opcode=INVOKESTATIC): @@ -95,15 +178,40 @@ PYPYUINTTODOUBLE = Method('pypy.PyPy', 'uint_to_double', '(I)D') PYPYDOUBLETOUINT = Method('pypy.PyPy', 'double_to_uint', '(D)I') PYPYLONGBITWISENEGATE = Method('pypy.PyPy', 'long_bitwise_negate', '(L)L') +PYPYARRAYTOLIST = Method('pypy.PyPy', 'array_to_list', + '([Ljava/lang/Object;)Ljava/util/List;') +PYPYSTRTOINT = Method('pypy.PyPy', 'str_to_int', + '([Ljava/lang/String;)I') +PYPYSTRTOUINT = Method('pypy.PyPy', 'str_to_uint', + '([Ljava/lang/String;)I') +PYPYSTRTOLONG = Method('pypy.PyPy', 'str_to_long', + '([Ljava/lang/String;)J') +PYPYSTRTOULONG = Method('pypy.PyPy', 'str_to_ulong', + '([Ljava/lang/String;)J') +PYPYSTRTOBOOL = Method('pypy.PyPy', 'str_to_bool', + '([Ljava/lang/String;)B') +PYPYSTRTODOUBLE = Method('pypy.PyPy', 'str_to_double', + '([Ljava/lang/String;)D') +PYPYSTRTOCHAR = Method('pypy.PyPy', 'str_to_char', + '([Ljava/lang/String;)C')
class JVMGenerator(Generator):
""" Base class for all JVM generators. Invokes a small set of '_' methods which indicate which opcodes to emit; these can be - translated by a subclass into Jasmin assembly, binary output, etc.""" + translated by a subclass into Jasmin assembly, binary output, etc. + Must be inherited from to specify a particular output format; + search for the string 'unimplemented' to find the methods that + must be overloaded. """ + + def __init__(self, type_system): + self.type_system = type_system
# __________________________________________________________________ # JVM specific methods to be overloaded by a subclass + # + # If the name does not begin with '_', it will be called from + # outside the generator.
def begin_class(self, classnm): """ @@ -114,64 +222,213 @@ def end_class(self): unimplemented
- def begin_function(self, funcname, argtypes, static=False): + def add_field(self, fname, ftype): + """ + fname --- name of the field (a string) + ftype --- JvmType for the field + """ + # TODO --- should fdesc be an ootype?? + unimplemented + + def begin_function(self, funcname, argvars, argtypes, rettype, + static=False): """ funcname --- name of the function - argtypes --- types of each argument (in what format??) + argvars --- list of objects passed to load() that represent arguments; + should be in order, or () if load() will not be used + argtypes --- JvmType for each argument + rettype --- JvmType for the return value static --- keyword, if true then a static func is generated + + This function also defines the scope for variables passed to + load()/store(). """ - unimplemented + # Compute the indicates of each argument in the local variables + # for the function. Note that some arguments take up two slots + # depending on their type [this is compute by type_width()] + self.next_offset = 0 + self.local_vars = {} + for idx, var in enumerate(argvars): + self.local_vars[var] = self.next_offset + self.next_offset += argtypes[idx].type_width() + # Prepare a map for the local variable indices we will add + # Let the subclass do the rest of the work; note that it does + # not need to know the argvars parameter, so don't pass it + self._begin_function(funcname, argtypes, rettype, static) + + def _begin_function(self, funcname, argtypes, rettype, static): + """ + Main implementation of begin_function. The begin_function() + does some generic handling of args. + """ + unimplemented
def end_function(self): + del self.next_offset + del self.local_vars + self._end_function() + + def _end_function(self): + unimplemented + + def mark(self, lbl): + """ Marks the point that a label indicates. """ unimplemented
- def _unique_label(self, desc): + def _instr(self, opcode, *args): + """ Emits an instruction with the given opcode and arguments. + The correct opcode and their types depends on the opcode. """ + unimplemented + + def return_val(self, vartype): + """ Returns a value from top of stack of the JvmType 'vartype' """ + self._instr(RETURN.for_type(vartype)) + + def load_jvm_var(self, vartype, varidx): + """ Loads from jvm slot #varidx, which is expected to hold a value of + type vartype """ + self._instr(LOAD.for_type(vartype), varidx) + + def store_jvm_var(self, vartype, varidx): + """ Loads from jvm slot #varidx, which is expected to hold a value of + type vartype """ + self._instr(STORE.for_type(vartype), varidx) + + def load_from_array(self, elemtype): + """ Loads something from an array; the result will be of type 'elemtype' + (and hence the array is of type 'array_of(elemtype)'), where + 'elemtype' is a JvmType. Assumes that the array ref and index are + already pushed onto stack (in that order). """ + self._instr(ARRLOAD.for_type(elemtype)) + + def store_to_array(self, elemtype): + """ Stores something into an array; the result will be of type + 'elemtype' (and hence the array is of type + 'array_of(elemtype)'), where 'elemtype' is a JvmType. Assumes + that the array ref, index, and value are already pushed onto + stack (in that order).""" + self._instr(ARRLOAD.for_type(elemtype)) + + def unique_label(self, desc, mark=False): """ Returns an opaque, unique label object that can be passed an - argument for branching opcodes, or the _mark instruction. + argument for branching opcodes, or the mark instruction.
'desc' should be a comment describing the use of the label. It is for decorative purposes only and should be a valid C - identifier.""" + identifier. + + 'mark' --- if True, then also calls self.mark() with the new lbl """ labelnum = len(self._labels) self._labels.append(desc) - return ('Label', labelnum) + res = ('Label', labelnum) + if mark: + self.mark(res) + return res + + # __________________________________________________________________ + # Exception Handling
- def _mark(self, lbl): - """ Marks the point that a label indicates. """ - unimplemented + def begin_try(self): + """ + Begins a try/catch region. Must be followed by a call to end_try() + after the code w/in the try region is complete. + """ + self.begintrylbl = self.unique_label("begin_try", mark=True)
- def _instr(self, opcode, *args): - """ Emits an instruction with the given opcode and arguments. - The correct opcode and their types depends on the opcode. """ - unimplemented + def end_try(self): + """ + Ends a try/catch region. Must be followed immediately + by a call to begin_catch(). + """ + self.endtrylbl = self.unique_label("end_try", mark=True) + + def begin_catch(self, excclsty): + """ + Begins a catch region corresponding to the last try; there can + be more than one call to begin_catch, in which case the last + try region is reused. + 'excclsty' --- a JvmType for the class of exception to be caught + """ + catchlbl = self.unique_label("catch") + self.mark(catchlbl, mark=True) + self.try_catch_region( + excclsty, self.begintrylbl, send.endtrylbl, catchlbl)
+ def end_catch(self): + """ + Ends a catch region. + (Included for CLI compatibility) + """ + return + + def try_catch_region(self, excclsty, trystartlbl, tryendlbl, catchlbl): + """ + Indicates a try/catch region: + 'excclsty' --- a JvmType for the class of exception to be caught + 'trystartlbl', 'tryendlbl' --- labels marking the beginning and end + of the try region + 'catchlbl' --- label marking beginning of catch region + """ + unimplemented + # __________________________________________________________________ # Generator methods and others that are invoked by MicroInstructions # # These translate into calls to the above methods.
def emit(self, instr, *args): - """ 'instr' in our case must be the name of another method, or - a JVM opcode (as named above) """ - - if hasattr(self, instr): + """ 'instr' in our case must be either a string, in which case + it is the name of a method to invoke, or an Opcode/Method + object (defined above).""" + + if isinstance(instr, str): return getattr(self, instr)(*args) - - glob = globals() - if instr in glob: - val = glob[instr] - if isinstance(val, Opcode): - self._instr(glob[opcode], *args) - else if isinstance(val, Method): - val.invoke(self)
- assert False + if isinstance(instr, Opcode): + return self._instr(instr, *args) + + if isinstance(instr, Method): + return instr.invoke(self) + + raise Exception("Unknown object in call to emit(): "+repr(instr)) + + def _var_data(self, v): + # Determine java type: + jty = self.type_system.ootype_to_jvm(v.concretetype) + # Determine index in stack frame slots: + # note that arguments and locals can be treated the same here + if v in self.local_vars: + idx = self.local_vars[v] + else: + idx = self.local_vars[v] = self.next_offset + self.next_offset += jty.type_width() + return jty, idx
def load(self, v): - unimplemented + if isinstance(v, flowmodel.Variable): + jty, idx = _var_data(v) + return self.load_jvm_var(jty, idx) + + if isinstance(v, flowmodel.Constant): + # TODO: Refactor and complete this code? Maybe more like cli code? + if TYPE is ootype.Void: + pass + elif TYPE is ootype.Bool: + self._instr(ICONST, int(value)) + elif TYPE is ootype.Char or TYPE is ootype.UniChar: + self._instr(ICONST, ord(value)) + elif isinstance(value, CDefinedIntSymbolic): + self._instr(ICONST, DEFINED_INT_SYMBOLICS[value.expr]) + elif TYPE in (ootype.Signed, ootype.Unsigned): + self._instr(ICONST, value) # handle Unsigned better! + + raise Exception('Unexpected type for v in load(): '+v)
def store(self, v): - unimplemented + if isinstance(v, flowmodel.Variable): + jty, idx = _var_data(v) + return self.store_jvm_var(jty, idx) + raise Exception('Unexpected type for v in store(): '+v)
def set_field(self, concretetype, value): self._instr(SETFIELD, concretetype, value) @@ -185,6 +442,13 @@ # __________________________________________________________________ # Methods invoked directly by strings in jvm/opcode.py
+ def goto(self, lbl): + self._instr(GOTO, lbl) + + def throw(self): + """ Throw the object from top of the stack as an exception """ + self._instr(ATHROW) + def iabs(self): MATHIABS.invoke(self)
@@ -196,6 +460,10 @@ self._instr(ICONST, -1) self._instr(IXOR)
+ def goto_if_true(self, label): + """ Jumps if the top of stack is true """ + self._instr(IFNE, label) + ##### Comparison methods
def _compare_op(self, cmpopcode): @@ -206,14 +474,14 @@ instruction equals zero]. Consumes as many operands from the stack as the cmpopcode consumes, typically 1 or 2. """ - midlbl = self._unique_label() - endlbl = self._unique_label() + midlbl = self.unique_label('cmpop') + endlbl = self.unique_label('cmpop') self._instr(cmpopcode, midlbl) self._instr(ICONST, 0) self._instr(GOTO, endlbl) - self._mark(midlbl) + self.mark(midlbl) self._instr(ICONST, 1) - self._mark(endlbl) + self.mark(endlbl)
logical_not = lambda self: self._compare_op(IFEQ) equals_zero = logical_not @@ -271,4 +539,72 @@ ulong_greater_equals = lambda self: self._ulong_compare_op(IFGE)
class JasminGenerator(JVMGenerator): - pass + + def __init__(self, outdir, package): + self.outdir = outdir + + def begin_class(self, classnm): + """ + classnm --- full Java name of the class (i.e., "java.lang.String") + """ + + iclassnm = classnm.replace('.', '/') + jfile = "%s/%s.j" % (self.outdir, iclassnm) + + try: + jdir = jfile[:jfile.rindex('/')] + os.makedirs(jdir) + except OSError: pass + self.out = open(jfile, 'w') + + # Write the JasminXT header + self.out.write(".bytecode XX\n") + #self.out.write(".source \n") + self.out.write(".class public %s\n" % iclassnm) + self.out.write(".super java/lang/Object\n") # ? + + def end_class(self): + self.out.close() + self.out = None + + def add_field(self, fname, fdesc): + # TODO --- Signature for generics? + # TODO --- these must appear before methods, do we want to buffer + # them up to allow out of order calls to add_field()? + assert isinstance(fdesc, JvmType) + self.out.write('.field public %s %s\n' % (fname, fdesc)) + + def _begin_function(self, funcname, argtypes, rettype, static): + # Throws clause? Only use RuntimeExceptions? + kw = ['public'] + if static: kw.append('static') + self.out.write('.method %s %s (%s)%s\n' % ( + funcname, " ".join(kw), + "".join(argtypes), rettype)) + + def _end_function(self): + self.out.write('.end method\n') + + def mark(self, lbl): + """ Marks the point that a label indicates. """ + _, lblnm = lbl + assert _ == "Label" + self.out.write(' %s:\n' % lblnm) + + def _instr(self, opcode, *args): + jvmstr = opcode.jvmstr + + # Hack: this should be somewhere else, just not sure where yet + if opcode.flags & CONSTSPEC: + if args[0] == -1: + jvmstr += "_m1" + elif args[0] >= 0 and args[0] <= 5: + jvmstr += "_%d" % args[0] + + self.out.write(' %s %s\n' % ( + jvmstr, " ".join([str(s) for s in args]))) + + def try_catch_region(self, excclsty, trystartlbl, tryendlbl, catchlbl): + self.out.write(' .catch %s from %s to %s using %s\n' % ( + excclsty.int_class_name(), trystartlbl, tryendlbl, catchlbl)) +
Modified: pypy/dist/pypy/translator/jvm/genjvm.py ============================================================================== --- pypy/dist/pypy/translator/jvm/genjvm.py (original) +++ pypy/dist/pypy/translator/jvm/genjvm.py Sun Oct 22 01:36:59 2006 @@ -11,7 +11,9 @@ from pypy.translator.jvm.generator import JasminGenerator from pypy.translator.jvm.option import getoption from pypy.translator.jvm.database import Database +from pypy.translator.jvm.typesystem import JvmTypeSystem from pypy.translator.jvm.log import log +from pypy.translator.jvm.node import EntryPoint
class JvmError(Exception): """ Indicates an error occurred in the JVM runtime """ @@ -26,9 +28,13 @@
For those interested in the location of the files, the following attributes exist: - tmpdir --- root directory from which all files can be found - javadir --- the directory containing *.java - classdir --- the directory where *.class will be generated + tmpdir --- root directory from which all files can be found (py.path obj) + javadir --- the directory containing *.java (py.path obj) + classdir --- the directory where *.class will be generated (py.path obj) + package --- a string with the name of the package (i.e., 'java.util') + + The following attributes also exist to find the state of the sources: + compiled --- True once the sources have been compiled successfully """
def __init__(self, tmpdir, package): @@ -39,11 +45,12 @@ """ self.tmpdir = tmpdir self.package = package + self.compiled = False
# Compute directory where .java files are self.javadir = self.tmpdir for subpkg in package.split('.'): - self.srcdir = os.path.join(self.srcdir, subpkg) + self.javadir = self.javadir.join(subpkg)
# Compute directory where .class files should go self.classdir = self.javadir @@ -51,12 +58,14 @@ def compile(self): """ Compiles the .java sources into .class files, ready for execution. + Raises a JvmError if compilation fails. """ javac = getoption('javac') - javafiles = [f for f in os.listdir(self.javadir) + javafiles = [f for f in self.javadir.listdir() if f.endswith('.java')] res = subprocess.call([javac] + javafiles) if res: raise JvmError('Failed to compile!') + else: self.compiled = True
def execute(self, args): """ @@ -64,6 +73,7 @@ output as a string. The 'args' are provided as arguments, and will be converted to strings. """ + assert self.compiled strargs = [str(a) for a in args] cmd = [getoption('java'), '%s.Main' % self.package] pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout @@ -81,12 +91,12 @@ t = TranslationContext() ann = t.buildannotator() ann.build_types(func, annotation) - t.buildrtype(type_system="ootype").specialize() + t.buildrtyper(type_system="ootype").specialize() main_graph = t.graphs[0] if getoption('view'): t.view() if getoption('wd'): tmpdir = py.path.local('.') else: tmpdir = udir - jvm = GenJvm(tmpdir, t) + jvm = GenJvm(tmpdir, t, entrypoint=EntryPoint(main_graph, True)) return jvm.generate_source()
class GenJvm(object): @@ -104,19 +114,18 @@ 'entrypoint' --- if supplied, an object with a render method """ self.jvmsrc = JvmGeneratedSource(tmpdir, getoption('package')) - self.db = Database() + self.type_system = JvmTypeSystem() + self.db = Database(self.type_system) if entrypoint: self.db.pending_node(entrypoint) + else: + self.db.pending_node(EntryPoint(translator.graphs[0], False))
def generate_source(self): """ Creates the sources, and returns a JvmGeneratedSource object for manipulating them """ generator = self._create_generator()
- # Deal with entry point - if not self.db.len_pending(): - # XXX default entry point - # Drain worklist n = 0 while self.db.len_pending(): @@ -129,13 +138,12 @@ (n, total, n*100.0/total))
# Return the source object once we have finished - generator.all_done() return self.jvmsrc
def _create_generator(self): """ Creates and returns a Generator object according to the configuration. Right now, however, there is only one kind of generator: JasminGenerator """ - return JasminGenerator(self.jvmsrc.javadir) + return JasminGenerator(self.jvmsrc.javadir, self.jvmsrc.package)
Modified: pypy/dist/pypy/translator/jvm/node.py ============================================================================== --- pypy/dist/pypy/translator/jvm/node.py (original) +++ pypy/dist/pypy/translator/jvm/node.py Sun Oct 22 01:36:59 2006 @@ -1,3 +1,16 @@ +""" +Rendering nodes for the JVM. I suspect that a lot of this could be +made to be common between CLR and JVM. +""" + + +from pypy.rpython.lltypesystem import lltype +from pypy.rpython.ootypesystem import ootype +from pypy.translator.jvm.typesystem import jStringArray, jVoid, jThrowable +from pypy.translator.jvm.typesystem import jvm_for_class +import pypy.translator.jvm.generator as jvmgen +from pypy.translator.jvm.opcodes import opcodes + class Node(object): def render(self, db, generator): unimplemented @@ -10,22 +23,256 @@ testing (see __init__) """
- def __init__(self, graph): + def __init__(self, graph, expandargs): """ 'graph' --- The initial graph to invoke from main() + 'expandargs' --- controls whether the arguments passed to main() + are passed as a list, or expanded to match each argument to the graph + + The 'expandargs' option deserves explanation: + + it will be false for a standalone build, because in that + case we want to convert the String[] array that main() receives + into a corresponding python List of string objects. + + it will (generally) be true when compiling individual + functions, in which case we might be compiling an entry + point with a signature like (a:int,b:float) in which case + argv[1] should be converted to an integer, and argv[2] + should be converted to a float. """ self.graph = graph + self.expand_arguments = expandargs pass
+ # XXX --- perhaps this table would be better placed in typesystem.py + # so as to constrain the knowledge of lltype and ootype + _type_conversion_methods = { + ootype.Signed:jvmgen.PYPYSTRTOINT, + ootype.Unsigned:jvmgen.PYPYSTRTOUINT, + lltype.SignedLongLong:jvmgen.PYPYSTRTOLONG, + lltype.UnsignedLongLong:jvmgen.PYPYSTRTOULONG, + ootype.Bool:jvmgen.PYPYSTRTOBOOL, + ootype.Float:jvmgen.PYPYSTRTODOUBLE, + ootype.Char:jvmgen.PYPYSTRTOCHAR + } + def render(self, db, gen): gen.begin_class('pypy.Main') - gen.begin_function('main', 'String[]', static=True) + gen.begin_function('main', (), [jStringArray], jVoid, static=True)
- # XXX --- handle arguments somehow! (will probably need some options) + # Handle arguments: + if self.expand_arguments: + # Convert each entry into the array to the desired type by + # invoking an appropriate helper function on each one + for i, arg in enumerate(self.graph.getargs()): + gen.emit(jvmgen.ICONST, i) + gen.emit(self._type_conversion_methods[arg.concretetype]) + else: + # Convert the array of strings to a List<String> as the + # python method expects + arg0 = self.graph.getargs()[0] + assert isinstance(arg0.concretetype, ootype.List), str(arg0.concretetype) + assert arg0._ITEMTYPE is ootype.String + gen.load_jvm_var(0) + gen.emit(jvmgen.PYPYARRAYTOLIST)
# Generate a call to this method - db.method_for_graph(self.graph).invoke(gen) + gen.emit(db.method_for_graph(self.graph, static=True))
gen.end_function() gen.end_class()
+class Function(object): + + """ Represents a function to be emitted. *Note* that it is not a + descendant of Node: it cannot be entered into the database + worklist. This is because in Java, all functions occur w/in a + class: therefore classes as a whole must be placed on the + worklist. """ + + def __init__(self, classobj, name, jargtypes, jrettype, graph, is_static): + """ + classobj: the Class object this is a part of (even static + functions have a class) + name: the name of the function + jargtypes: JvmType of each argument + jrettype: JvmType this function returns + graph: the graph representing the body of the function + is_static: boolean flag indicate whether func is static (!) + """ + self.classnm = classnm + self.name = name + self.graph = graph + self.jargtypes = jargtypes + self.jrettype = jrettype + self.is_static = is_static + + def method(self): + """ Returns a jvmgen.Method that can invoke this function """ + if self.is_static: opcode = jvmgen.INVOKESTATIC + else: opcode = jvmgen.INVOKEVIRTUAL + mdesc = jvm_method_desc(self.jargtypes, self.jrettype) + return jvmgen.Method(classnm, self.func_name, mdesc, opcode=opcode) + + def render_func(self, db, gen): + if getattr(self.graph.func, 'suggested_primitive', False): + assert False, 'Cannot render a suggested_primitive' + + # Prepare argument lists for begin_function call + jargvars = [] + jargtypes = [] + for arg in self.graph.getargs(): + if arg.concretetype is ootype.Void: continue + jargvars.append(arg) + jargtypes.append(db.type_system.ootype_to_jvm(arg.concretetype)) + + # Determine return type + jrettype = db.type_system.ootype_to_jvm( + self.graph.getreturnvar().concretetype) + + # Start the function definition + gen.begin_function(self.name, jargvars, jargtypes, jrettype, + static=self.is_static) + + # Go through each block and create a label for it; if the + # block will be used to catch an exception, add a second label + # to catch_labels + block_labels = {} + #catch_labels = {} + for ctr, block in enumerate(graph.iterblocks()): + blbl = gen.unique_label('Block_'+ctr) + block_labels[block] = blbl + + ## Go through the blocks we may toss exceptions to + #if block.exitswitch == flowmodel.c_last_exception: + # for link in block.exits: + # if link.exitcase is None: continue # return + # if link.target not in catch_labels: + # catch_labels[link.target] = gen.unique_label('catch') + + # Iterate through the blocks and generate code for them + return_blocks = [] + for block in graph.iterblocks(): + + # Mark the beginning of the block, render all the ops, and + # then mark the end. + gen.mark(block_labels[block][0]) + + # Determine whether the last oper in this block may raise an exc + handle_exc = (block.exitswitch == flowmodel.c_last_exception) + + # Render the operations; create labels for a try/catch + # region around the last operation + if block.operations: + for op in block.operations[:-1]: + self._render_op(op) + if handle_exc: trybeglbl = gen.unique_label('try', mark=True) + self._render_op(block.operations[-1]) + if handle_exc: tryendlbl = gen.unique_label('try', mark=True) + + # Handle 'return' blocks: in this case, we return the + # variable specified + if self._is_return_block(block): + return_var = block.inputargs[0] + return_ty = ootype_to_jvm(return_var.concretetype) + if return_var.concretetype is not Void: + self.load(return_var) + gen.return_val(return_ty) + + # Handle 'raise' blocks: in this case, we just throw the + # variable specified + if self._is_raise_block(block): + exc = block.inputargs[1] + self.load(exc) + gen.throw() + + if handle_exc: + # search for the "default" block to be executed when + # no exception is raised + for link in block.exits: + if link.exitcase is None: + self._copy_link_vars(gen, link) + gen.goto(block_labels[link.target]) + + # TODO: proper exception handling; we may not want to + # use the same model as CLR + else: + # no exception handling, determine correct link to follow + for link in block.exits: + self._copy_link_vars(gen, link) + target_label = block_labels[link.target] + if link.exitcase is None or link is block.exits[-1]: + gen.goto(target_label) + else: + assert type(link.exitcase is bool) + assert block.exitswitch is not None + gen.load(block.exitswitch) + gen.goto_if_true(target_label) + + gen.end_function() + + def _render_op(self, op): + instr_list = opcodes.get(op.opname, None) + assert getoption('nostop') or instr_list is not None + if instr_list: instr_list.render(self, op) + + def _copy_link_vars(self, gen, link): + target = link.target + for to_load, to_store in zip(link.args, target.inputargs): + if to_load.concretetype is not Void: + gen.load(to_load) + gen.store(to_store) + + def _is_return_block(self, block): + return (not block.exits) and len(block.inputargs) == 1 + + def _is_raise_block(self, block): + return (not block.exits) and len(block.inputargs) == 2 + +class Class(Node): + + """ Represents a class to be emitted. Note that currently, classes + are emitted all in one shot, not piecemeal. """ + + def __init__(self, name): + """ + 'name' should be a fully qualified Java class name like + "java.lang.String" + """ + self.name = name + self.fields = [] + self.methods = {} # Maps graph -> Function + self.rendered = False + + def jvm_type(self): + return jvm_for_class(self.name) + + def add_field(self, fieldty, fieldnm): + """ Creates a new field in this with type 'fieldty' (a + JvmType) and with the name ;fieldnm; (a String). Must be called + before render().""" + assert not self.rendered + self.fields.append((fieldty, fieldnm)) + + def has_method_for(self, graph): + return graph in self.methods + + def add_method(self, func): + """ Creates a new method in this class, represented by the + Function object 'func'. Must be called before render(); + intended to be invoked by the database.""" + assert not self.rendered + self.methods[func.graph] = func + + def render(self, db, gen): + self.rendered = True + gen.begin_class(self.name) + + for fieldty, fieldnm in self.fields: + gen.add_field(fieldty, fieldnm) + + for method in self.methods.values(): + method.render_func(db, gen) + + gen.end_class(self.name)
Modified: pypy/dist/pypy/translator/jvm/opcodes.py ============================================================================== --- pypy/dist/pypy/translator/jvm/opcodes.py (original) +++ pypy/dist/pypy/translator/jvm/opcodes.py Sun Oct 22 01:36:59 2006 @@ -5,41 +5,48 @@
"""
-from pypy.translator.cli.metavm import Call, CallMethod, RuntimeNew, \ - IndirectCall, GetField, SetField, CastTo, OOString, DownCast, NewCustomDict,\ - CastWeakAdrToPtr, MapException -from pypy.translator.oosupport.metavm import PushArg, PushAllArgs, StoreResult, InstructionList,\ - New -from pypy.translator.cli.cts import WEAKREF +from pypy.translator.oosupport.metavm import \ + PushArg, PushAllArgs, StoreResult, InstructionList, New, DoNothing +import pypy.translator.jvm.generator as jvmgen + +def _check_zer(op): + # TODO + return op + +def _check_ovf(op): + # TODO + return op
+# This table maps the opcodes to micro-ops for processing them. +# It is post-processed by a function to be found below. opcodes = { # __________ object oriented operations __________ - 'new': [New], - 'runtimenew': [RuntimeNew], - 'oosetfield': [SetField], - 'oogetfield': [GetField], - 'oosend': [CallMethod], - 'ooupcast': DoNothing, - 'oodowncast': [DownCast], - 'oois': 'ceq', - 'oononnull': [PushAllArgs, 'ldnull', 'ceq']+Not, - 'instanceof': [CastTo, 'ldnull', 'cgt.un'], - 'subclassof': [PushAllArgs, 'call bool [pypylib]pypy.runtime.Utils::SubclassOf(class [mscorlib]System.Type, class[mscorlib]System.Type)'], - 'ooidentityhash': [PushAllArgs, 'callvirt instance int32 object::GetHashCode()'], - 'oohash': [PushAllArgs, 'callvirt instance int32 object::GetHashCode()'], - 'oostring': [OOString], - 'ooparse_int': [PushAllArgs, 'call int32 [pypylib]pypy.runtime.Utils::OOParseInt(string, int32)'], - 'oonewcustomdict': [NewCustomDict], - - 'same_as': DoNothing, - 'hint': [PushArg(0), StoreResult], - 'direct_call': [Call], - 'indirect_call': [IndirectCall], - - 'cast_ptr_to_weakadr': [PushAllArgs, 'newobj instance void class %s::.ctor(object)' % WEAKREF], - 'cast_weakadr_to_ptr': [CastWeakAdrToPtr], - 'gc__collect': 'call void class [mscorlib]System.GC::Collect()', - 'resume_point': Ignore, + #'new': [New], + #'runtimenew': [RuntimeNew], + #'oosetfield': [SetField], + #'oogetfield': [GetField], + #'oosend': [CallMethod], + #'ooupcast': DoNothing, + #'oodowncast': [DownCast], + #'oois': 'ceq', + #'oononnull': [PushAllArgs, 'ldnull', 'ceq']+Not, + #'instanceof': [CastTo, 'ldnull', 'cgt.un'], + #'subclassof': [PushAllArgs, 'call bool [pypylib]pypy.runtime.Utils::SubclassOf(class [mscorlib]System.Type, class[mscorlib]System.Type)'], + #'ooidentityhash': [PushAllArgs, 'callvirt instance int32 object::GetHashCode()'], + #'oohash': [PushAllArgs, 'callvirt instance int32 object::GetHashCode()'], + #'oostring': [OOString], + #'ooparse_int': [PushAllArgs, 'call int32 [pypylib]pypy.runtime.Utils::OOParseInt(string, int32)'], + #'oonewcustomdict': [NewCustomDict], + # + #'same_as': DoNothing, + #'hint': [PushArg(0), StoreResult], + #'direct_call': [Call], + #'indirect_call': [IndirectCall], + # + #'cast_ptr_to_weakadr': [PushAllArgs, 'newobj instance void class %s::.ctor(object)' % WEAKREF], + #'cast_weakadr_to_ptr': [CastWeakAdrToPtr], + #'gc__collect': 'call void class [mscorlib]System.GC::Collect()', + #'resume_point': Ignore,
# __________ numeric operations __________
@@ -56,126 +63,132 @@ 'unichar_ne': 'not_equals',
'int_is_true': 'not_equals_zero', - 'int_neg': 'INEG', + 'int_neg': jvmgen.INEG, 'int_neg_ovf': None, # How to handle overflow? 'int_abs': 'iabs', 'int_abs_ovf': _check_ovf('iabs'), 'int_invert': 'bitwise_negate',
- 'int_add': 'IADD', - 'int_sub': 'ISUB', - 'int_mul': 'IMUL', - 'int_floordiv': 'IDIV', - 'int_floordiv_zer': _check_zer('IDIV'), - 'int_mod': 'IREM', + 'int_add': jvmgen.IADD, + 'int_sub': jvmgen.ISUB, + 'int_mul': jvmgen.IMUL, + 'int_floordiv': jvmgen.IDIV, + 'int_floordiv_zer': _check_zer(jvmgen.IDIV), + 'int_mod': jvmgen.IREM, 'int_lt': 'less_than', 'int_le': 'less_equals', 'int_eq': 'equals', 'int_ne': 'not_equals', 'int_gt': 'greater_than', 'int_ge': 'greater_equals', - 'int_and': 'IAND', - 'int_or': 'IOR', - 'int_lshift': 'ISHL', - 'int_rshift': 'ISHR', - 'int_xor': 'IXOR', - 'int_add_ovf': _check_ovf('IADD'), - 'int_sub_ovf': _check_ovf('ISUB'), - 'int_mul_ovf': _check_ovf('IMUL'), - 'int_floordiv_ovf': 'IDIV', # these can't overflow! - 'int_mod_ovf': 'IREM', + 'int_and': jvmgen.IAND, + 'int_or': jvmgen.IOR, + 'int_lshift': jvmgen.ISHL, + 'int_rshift': jvmgen.ISHR, + 'int_xor': jvmgen.IXOR, + 'int_add_ovf': _check_ovf(jvmgen.IADD), + 'int_sub_ovf': _check_ovf(jvmgen.ISUB), + 'int_mul_ovf': _check_ovf(jvmgen.IMUL), + 'int_floordiv_ovf': jvmgen.IDIV, # these can't overflow! + 'int_mod_ovf': jvmgen.IREM, 'int_lt_ovf': 'less_than', 'int_le_ovf': 'less_equals', 'int_eq_ovf': 'equals', 'int_ne_ovf': 'not_equals', 'int_gt_ovf': 'greater_than', 'int_ge_ovf': 'greater_equals', - 'int_and_ovf': 'IAND', - 'int_or_ovf': 'IOR', + 'int_and_ovf': jvmgen.IAND, + 'int_or_ovf': jvmgen.IOR,
- 'int_lshift_ovf': _check_ovf('ISHL'), - 'int_lshift_ovf_val': _check_ovf('ISHL'), # VAL?? + 'int_lshift_ovf': _check_ovf(jvmgen.ISHL), + 'int_lshift_ovf_val': _check_ovf(jvmgen.ISHL), # VAL??
- 'int_rshift_ovf': 'ISHR', # these can't overflow! - 'int_xor_ovf': 'IXOR', - 'int_floordiv_ovf_zer': _check_zer('IDIV'), - 'int_mod_ovf_zer': _check_zer('IREM'), + 'int_rshift_ovf': jvmgen.ISHR, # these can't overflow! + 'int_xor_ovf': jvmgen.IXOR, + 'int_floordiv_ovf_zer': _check_zer(jvmgen.IDIV), + 'int_mod_ovf_zer': _check_zer(jvmgen.IREM),
'uint_is_true': 'not_equals_zero', 'uint_invert': 'bitwise_negate',
- 'uint_add': 'IADD', - 'uint_sub': 'ISUB', - 'uint_mul': 'IMUL', - 'uint_div': 'IDIV', # valid? + 'uint_add': jvmgen.IADD, + 'uint_sub': jvmgen.ISUB, + 'uint_mul': jvmgen.IMUL, + 'uint_div': jvmgen.IDIV, # valid? 'uint_truediv': None, # TODO - 'uint_floordiv': 'IDIV', # valid? - 'uint_mod': 'IREM', # valid? + 'uint_floordiv': jvmgen.IDIV, # valid? + 'uint_mod': jvmgen.IREM, # valid? 'uint_lt': 'u_less_than', 'uint_le': 'u_less_equals', 'uint_eq': 'u_equals', 'uint_ne': 'u_not_equals', 'uint_gt': 'u_greater_than', 'uint_ge': 'u_greater_equals', - 'uint_and': 'IAND', - 'uint_or': 'IOR', - 'uint_lshift': 'ISHL', - 'uint_rshift': 'IUSHR', - 'uint_xor': 'IXOR', - - 'float_is_true': [PushAllArgs, 'DCONST_0', 'dbl_not_equals'], - 'float_neg': 'DNEG', + 'uint_and': jvmgen.IAND, + 'uint_or': jvmgen.IOR, + 'uint_lshift': jvmgen.ISHL, + 'uint_rshift': jvmgen.IUSHR, + 'uint_xor': jvmgen.IXOR, + + 'float_is_true': [PushAllArgs, + jvmgen.DCONST_0, + 'dbl_not_equals'], + 'float_neg': jvmgen.DNEG, 'float_abs': 'dbl_abs',
- 'float_add': 'DADD', - 'float_sub': 'DSUB', - 'float_mul': 'DMUL', - 'float_truediv': 'DDIV', - 'float_mod': 'DREM', # use Math.IEEEremainder? + 'float_add': jvmgen.DADD, + 'float_sub': jvmgen.DSUB, + 'float_mul': jvmgen.DMUL, + 'float_truediv': jvmgen.DDIV, + 'float_mod': jvmgen.DREM, # use Math.IEEEremainder? 'float_lt': 'dbl_less_than', 'float_le': 'dbl_less_equals', 'float_eq': 'dbl_equals', 'float_ne': 'dbl_not_equals', 'float_gt': 'dbl_greater_than', 'float_ge': 'dbl_greater_equals', - 'float_floor': 'MATHFLOOR', - 'float_fmod': 'DREM', # DREM is akin to fmod() in C + 'float_floor': jvmgen.MATHFLOOR, + 'float_fmod': jvmgen.DREM, # DREM is akin to fmod() in C
- 'llong_is_true': [PushAllArgs, 'LCONST_0', 'long_not_equals'], - 'llong_neg': 'LNEG', - 'llong_neg_ovf': _check_ovf('LNEG'), - 'llong_abs': 'MATHLABS', - 'llong_invert': 'PYPYLONGBITWISENEGATE', - - 'llong_add': 'LADD', - 'llong_sub': 'LSUB', - 'llong_mul': 'LMUL', - 'llong_div': 'LDIV', + 'llong_is_true': [PushAllArgs, + jvmgen.LCONST_0, + 'long_not_equals'], + 'llong_neg': jvmgen.LNEG, + 'llong_neg_ovf': _check_ovf(jvmgen.LNEG), + 'llong_abs': jvmgen.MATHLABS, + 'llong_invert': jvmgen.PYPYLONGBITWISENEGATE, + + 'llong_add': jvmgen.LADD, + 'llong_sub': jvmgen.LSUB, + 'llong_mul': jvmgen.LMUL, + 'llong_div': jvmgen.LDIV, 'llong_truediv': None, # TODO - 'llong_floordiv': 'LDIV', - 'llong_mod': 'LREM', + 'llong_floordiv': jvmgen.LDIV, + 'llong_mod': jvmgen.LREM, 'llong_lt': 'long_less_than', 'llong_le': 'long_less_equals', 'llong_eq': 'long_equals', 'llong_ne': 'long_not_equals', 'llong_gt': 'long_greater_than', 'llong_ge': 'long_greater_equals', - 'llong_and': 'LAND', - 'llong_or': 'LOR', - 'llong_lshift': 'LSHL', - 'llong_rshift': 'LSHR', - 'llong_xor': 'LXOR', - - 'ullong_is_true': [PushAllArgs, 'LCONST_0', 'long_not_equals'], - 'ullong_invert': 'PYPYLONGBITWISENEGATE', - - 'ullong_add': 'LADD', - 'ullong_sub': 'LSUB', - 'ullong_mul': 'LMUL', - 'ullong_div': 'LDIV', # valid? + 'llong_and': jvmgen.LAND, + 'llong_or': jvmgen.LOR, + 'llong_lshift': jvmgen.LSHL, + 'llong_rshift': jvmgen.LSHR, + 'llong_xor': jvmgen.LXOR, + + 'ullong_is_true': [PushAllArgs, + jvmgen.LCONST_0, + 'long_not_equals'], + 'ullong_invert': jvmgen.PYPYLONGBITWISENEGATE, + + 'ullong_add': jvmgen.LADD, + 'ullong_sub': jvmgen.LSUB, + 'ullong_mul': jvmgen.LMUL, + 'ullong_div': jvmgen.LDIV, # valid? 'ullong_truediv': None, # TODO - 'ullong_floordiv': 'LDIV', # valid? - 'ullong_mod': 'LREM', # valid? + 'ullong_floordiv': jvmgen.LDIV, # valid? + 'ullong_mod': jvmgen.LREM, # valid? 'ullong_lt': 'ulong_less_than', 'ullong_le': 'ulong_less_equals', 'ullong_eq': 'ulong_equals', @@ -189,19 +202,24 @@ # trick. 'cast_bool_to_int': DoNothing, 'cast_bool_to_uint': DoNothing, - 'cast_bool_to_float': [PushAllArgs, 'not_equals_zero', 'I2D'], + 'cast_bool_to_float': [PushAllArgs, 'not_equals_zero', jvmgen.I2D],
'cast_char_to_int': DoNothing, 'cast_unichar_to_int': DoNothing, 'cast_int_to_char': DoNothing, 'cast_int_to_unichar': DoNothing, 'cast_int_to_uint': DoNothing, - 'cast_int_to_float': 'I2D', - 'cast_int_to_longlong': 'I2L', + 'cast_int_to_float': jvmgen.I2D, + 'cast_int_to_longlong': jvmgen.I2L, 'cast_uint_to_int': DoNothing, - 'cast_uint_to_float': PYPYUINTTODOUBLE, - 'cast_float_to_int': 'D2I', - 'cast_float_to_uint': PYPYDOUBLETOUINT, - 'truncate_longlong_to_int': 'L2I', + 'cast_uint_to_float': jvmgen.PYPYUINTTODOUBLE, + 'cast_float_to_int': jvmgen.D2I, + 'cast_float_to_uint': jvmgen.PYPYDOUBLETOUINT, + 'truncate_longlong_to_int': jvmgen.L2I,
} + +for opc in opcodes: + val = opcodes[opc] + if not isinstance(val, list): + val = [PushAllArgs, val]
Added: pypy/dist/pypy/translator/jvm/option.py ============================================================================== --- (empty file) +++ pypy/dist/pypy/translator/jvm/option.py Sun Oct 22 01:36:59 2006 @@ -0,0 +1,4 @@ +from pypy.translator.jvm.conftest import option + +def getoption(name): + return getattr(option, name)
Modified: pypy/dist/pypy/translator/jvm/src/PyPy.java ============================================================================== --- pypy/dist/pypy/translator/jvm/src/PyPy.java (original) +++ pypy/dist/pypy/translator/jvm/src/PyPy.java Sun Oct 22 01:36:59 2006 @@ -1,12 +1,35 @@ package pypy;
+import java.util.List; +import java.util.ArrayList; + +/** + * Class with a number of utility routines. + */ public class PyPy {
+ /** + * Compares two unsigned integers (value1 and value2) and returns + * a value greater than, equal to, or less than zero if value 1 is + * respectively greater than, equal to, or less than value2. The + * idea is that you can do the following: + * + * Call uint_cmp(value1, value2) + * IFLT ... // jumps if value1 < value2 + * IFEQ ... // jumps if value1 == value2 + * IFGT ... // jumps if value1 > value2 + * etc + */ public static int uint_cmp(int value1, int value2) { - final int VALUE2BIGGER = -1; final int VALUE1BIGGER = 1; + final int VALUE2BIGGER = -1; final int EQUAL = 0;
+ if (((value1 | value2) & Integer.MIN_VALUE) == 0) { + // neither is negative, presumably the common case + return value1 - value2; + } + if (value1 == value2) return EQUAL;
@@ -23,13 +46,8 @@ return VALUE1BIGGER; } } - else if (value2 < 0) { - // value1 is not neg, value2 is neg - return VALUE2BIGGER; - } - - if (value1 > value2) - return VALUE1BIGGER; + + // value1 is not neg, value2 is neg return VALUE2BIGGER; }
@@ -92,4 +110,76 @@ public static long long_bitwise_negate(long value) { return ~value; } + + public static List<?> array_to_list(Object[] array) { + List<?> l = new ArrayList(); + for (Object o : array) { + l.add(o); + } + return l; + } + + public static int str_to_int(String s) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException fe) { + throw new RuntimeException(fe); + } + } + + public static int str_to_uint(String s) { + try { + long l = Long.parseLong(s); + if (l < Integer.MAX_VALUE) + return l; + int lowerword = l & 0xFFFF; + int upperword = l >> 16; + return lowerword + (upperword << 16); + } catch (NumberFormatException fe) { + throw new RuntimeException(fe); + } + } + + public static long str_to_long(String s) { + try { + return Long.parseLong(s); + } catch (NumberFormatException fe) { + throw new RuntimeException(fe); + } + } + + public static long str_to_ulong(String s) { + // oh bother + throw new RuntimeException("TODO--- str to ulong"); + } + + public static boolean str_to_bool(String s) { + // not sure what are considered valid boolean values... + // let's be very accepting and take both strings and numbers + if (s.equalsIgnoreCase("true")) + return true; + if (s.equalsIgnoreCase("false")) + return false; + + try { + int i = Integer.parseInt(s); + return i != 0; + } catch (NumberFormatException ex) { + throw new RuntimeException(ex); + } + } + + public static double str_to_double(String s) { + try { + return Double.parseDouble(s); + } catch (NumberFormatException ex) { + throw new RuntimeException(ex); + } + } + + public static char str_to_char(String s) { + if (s.length() != 1) + throw new RuntimeException("String not single character: '"+s+"'"); + return s.charAt(0); + } } \ No newline at end of file
Added: pypy/dist/pypy/translator/jvm/test/__init__.py ==============================================================================
Added: pypy/dist/pypy/translator/jvm/test/runtest.py ============================================================================== --- (empty file) +++ pypy/dist/pypy/translator/jvm/test/runtest.py Sun Oct 22 01:36:59 2006 @@ -0,0 +1,119 @@ +import os +import platform + +import py +from py.compat import subprocess +from pypy.tool.udir import udir +from pypy.rpython.test.tool import BaseRtypingTest, OORtypeMixin +from pypy.rpython.lltypesystem.lltype import typeOf +from pypy.rpython.ootypesystem import ootype +from pypy.annotation.model import lltype_to_annotation +from pypy.translator.translator import TranslationContext +from pypy.translator.jvm.genjvm import generate_source_for_function +from pypy.translator.jvm.option import getoption + +FLOAT_PRECISION = 8 + +# CLI duplicate +class StructTuple(tuple): + def __getattr__(self, name): + if name.startswith('item'): + i = int(name[len('item'):]) + return self[i] + else: + raise AttributeError, name + +# CLI duplicate +class OOList(list): + def ll_length(self): + return len(self) + + def ll_getitem_fast(self, i): + return self[i] + +# CLI duplicate +class ExceptionWrapper: + def __init__(self, class_name): + self.class_name = class_name + + def __repr__(self): + return 'ExceptionWrapper(%s)' % repr(self.class_name) + +# CLI could-be duplicate +class JvmGeneratedSourceWrapper(object): + def __init__(self, gensrc): + """ gensrc is an instance of JvmGeneratedSource """ + self.gensrc = gensrc + + def __call__(self, *args): + if not self.gensrc.compiled: + py.test.skip("Assembly disabled") + + if getoption('norun'): + py.test.skip("Execution disabled") + + resstr = self.gensrc.execute(args) + res = eval(resstr) + if isinstance(res, tuple): + res = StructTuple(res) # so tests can access tuple elements with .item0, .item1, etc. + elif isinstance(res, list): + res = OOList(res) + return res + +class JvmTest(BaseRtypingTest, OORtypeMixin): + def __init__(self): + self._func = None + self._ann = None + self._jvm_src = None + + def _compile(self, fn, args, ann=None): + if ann is None: + ann = [lltype_to_annotation(typeOf(x)) for x in args] + if self._func is fn and self._ann == ann: + return JvmGeneratedSourceWrapper(self._jvm_src) + else: + self._func = fn + self._ann = ann + self._jvm_src = generate_source_for_function(fn, ann) + if not getoption('noasm'): + self._jvm_src.compile() + return JvmGeneratedSourceWrapper(self._jvm_src) + + def _skip_win(self, reason): + if platform.system() == 'Windows': + py.test.skip('Windows --> %s' % reason) + + def interpret(self, fn, args, annotation=None): + py.test.skip("jvm tests don't work yet") + src = self._compile(fn, args, annotation) + res = src(*args) + if isinstance(res, ExceptionWrapper): + raise res + return res + + def interpret_raises(self, exception, fn, args): + import exceptions # needed by eval + try: + self.interpret(fn, args) + except ExceptionWrapper, ex: + assert issubclass(eval(ex.class_name), exception) + else: + assert False, 'function did not raise any exception at all' + + def float_eq(self, x, y): + return round(x, FLOAT_PRECISION) == round(y, FLOAT_PRECISION) + + def ll_to_string(self, s): + return s + + def ll_to_list(self, l): + return l + + def class_name(self, value): + return value.class_name.split(".")[-1] + + def is_of_instance_type(self, val): + return isinstance(val, InstanceWrapper) + + def read_attr(self, obj, name): + py.test.skip('read_attr not supported on genjvm tests')
Added: pypy/dist/pypy/translator/jvm/test/test_bool.py ============================================================================== --- (empty file) +++ pypy/dist/pypy/translator/jvm/test/test_bool.py Sun Oct 22 01:36:59 2006 @@ -0,0 +1,7 @@ +import py +from pypy.translator.jvm.test.runtest import JvmTest +from pypy.rpython.test.test_rbool import BaseTestRbool + +class TestJvmBool(JvmTest, BaseTestRbool): + pass +
Added: pypy/dist/pypy/translator/jvm/typesystem.py ============================================================================== --- (empty file) +++ pypy/dist/pypy/translator/jvm/typesystem.py Sun Oct 22 01:36:59 2006 @@ -0,0 +1,157 @@ +""" +Translation between PyPy ootypesystem and JVM type system. + +Here are some tentative non-obvious decisions: + +Signed scalar types mostly map as is. + +Unsigned scalar types are a problem; the basic idea is to store them +as signed values, but execute special code when working with them. Another +option would be to use classes, or to use the "next larger" type and remember to use appropriate modulos. The jury is out on +this. Another idea would be to add a variant type system that does +not have unsigned values, and write the required helper and conversion +methods in RPython --- then it could be used for multiple backends. + +Python strings are mapped to byte arrays, not Java Strings, since +Python strings are really sets of bytes, not unicode code points. +Jury is out on this as well; this is not the approach taken by cli, +for example. + +Python Unicode strings, on the other hand, map directly to Java Strings. + +WeakRefs can hopefully map to Java Weak References in a straight +forward fashion. + +Collections can hopefully map to Java collections instances. Note +that JVM does not have an idea of generic typing at its lowest level +(well, they do have signature attributes, but those don't really count +for much). + +""" +from pypy.rpython.lltypesystem import lltype, llmemory +from pypy.rpython.ootypesystem import ootype +from pypy.translator.jvm.option import getoption +from pypy.translator.jvm.log import log + +class JvmType(str): + """ + The class we use to represent JVM types; it is just a string with + the JVM type descriptor at the moment. Using JvmType allows us to + use isinstance, however. The grammar for type descriptors can be + read about here: + http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html + """ + def is_scalar(self): + return self[0] != 'L' and self[0] != '[' + def is_reference(self): + return not self.is_scalar() + def is_array(self): + return self[0] == '[' + def int_class_name(self): + """ Converts a descriptor like Ljava/lang/Object; to + java/lang/Object """ + assert self[0] == 'L' and self[-1] == ';' + return self[1:-1] + def type_width(self): + """ Returns number of JVM words this type takes up. JVM words + are a theoretically abstract quantity that basically + represents 32 bits; so most types are 1, but longs and doubles + are 2. """ + if self[0] == 'J' or self[0] == 'D': + return 2 + return 1 + +# JVM type functions + +def jvm_array_of(jtype): + """ Returns a JvmType representing an array of 'jtype', which must be + another JvmType """ + assert isinstance(jtype, JvmType) + return JvmType('['+str(jtype)) + +def jvm_for_class(classnm): + """ Returns a JvmType representing a particular class 'classnm', which + should be a fully qualified java class name (i.e., 'java.lang.String') """ + return JvmType('L%s;' % classnm.replace('.','/')) + +# Common JVM types +jVoid = JvmType('V') +jInt = JvmType('I') +jLong = JvmType('J') +jBool = JvmType('Z') +jDouble = JvmType('D') +jByte = JvmType('B') +jByteArray = jvm_array_of(jByte) +jChar = JvmType('C') +jThrowable = jvm_for_class('java.lang.Throwable') +jObject = jvm_for_class('java.lang.Object') +jString = jvm_for_class('java.lang.String') +jStringArray = jvm_array_of(jString) +jArrayList = jvm_for_class('java.util.ArrayList') +jHashMap = jvm_for_class('java.util.HashMap') +jIterator = jvm_for_class('java.util.Iterator') +jClass = jvm_for_class('java.lang.Class') +jStringBuilder = jvm_for_class('java.lang.StringBuilder') + +# Map from OOType to an internal JVM type descriptor +_lltype_to_jvm = { + ootype.Void: jVoid, + ootype.Signed: jInt, + ootype.Unsigned: jInt, + lltype.SignedLongLong: jLong, + lltype.UnsignedLongLong: jLong, + ootype.Bool: jBool, + ootype.Float: jDouble, + ootype.Char: jByte, + ootype.UniChar: jChar, + ootype.String: jByteArray, + ootype.ROOT: jObject, + + # We may want to use PyPy wrappers here later: + llmemory.WeakGcAddress: jObject, # XXX + ootype.StringBuilder: jStringBuilder, + ootype.Class: jClass, + ootype.List: jArrayList, + ootype.Dict: jHashMap, + ootype.DictItemsIterator:jIterator + } + +# Method descriptor construction +def jvm_method_desc(argtypes, rettype): + """ A Java method has a descriptor, which is a string specified + its argument and return types. This function converts a list of + argument types (JvmTypes) and the return type (also a JvmType), + into one of these descriptor strings. """ + return "(%s)%s" % ("".join(argtypes), rettype) + +class JvmTypeSystem(object): + + """ This object translates between the OOTypeSystem and JVM type + descriptors. """ + + def enforce_jvm(self, typ): + if isinstance(typ, JvmType): + return typ + return self.ootype_to_jvm(typ) + + def ootype_to_jvm(self, oot): + """ Returns an instance of JvmType corresponding to the given + OOType """ + + # Check the easy cases + if oot in _lltype_to_jvm: + return _lltype_to_jvm[oot] + + # Now handle the harder ones + if isinstance(oot, lltype.Ptr) and isinstance(t.TO, lltype.OpaqueType): + return jObject + if isinstance(oot, ootype.Instance): + return XXX + if isinstance(oot, ootype.Record): + return XXX + if isinstance(oot, ootype.StaticMethod): + return XXX + + # Uh-oh + unhandled_case +
Modified: pypy/dist/pypy/translator/oosupport/metavm.py ============================================================================== --- pypy/dist/pypy/translator/oosupport/metavm.py (original) +++ pypy/dist/pypy/translator/oosupport/metavm.py Sun Oct 22 01:36:59 2006 @@ -98,6 +98,10 @@
def __call__(self, *args): return self.render(*args) + +class _DoNothing(MicroInstruction): + def render(self, generator, op): + pass
class PushArg(MicroInstruction): """ Pushes a given operand onto the stack. """ @@ -227,3 +231,4 @@ SetField = _SetField() GetField = _GetField() DownCast = _DownCast() +DoNothing = _DoNothing() _______________________________________________ pypy-svn mailing list pypy-svn@codespeak.net http://codespeak.net/mailman/listinfo/pypy-svn
Ugh. You've broken a lot of stuff by just copying conftest.py from cli. Basically you cannot do that, because both are loaded at the same time (the reason for that is unclear) and options are conflicting.
participants (1)
-
Maciek Fijalkowski