[pypy-svn] r33945 - in pypy/dist/pypy/translator: cli jvm jvm/src jvm/test oosupport
niko at codespeak.net
niko at codespeak.net
Tue Oct 31 13:58:47 CET 2006
Author: niko
Date: Tue Oct 31 13:58:47 2006
New Revision: 33945
Added:
pypy/dist/pypy/translator/jvm/test/test_snippet.py
Modified:
pypy/dist/pypy/translator/cli/function.py
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/option.py
pypy/dist/pypy/translator/jvm/src/PyPy.java
pypy/dist/pypy/translator/jvm/test/runtest.py
pypy/dist/pypy/translator/jvm/test/test_bool.py
pypy/dist/pypy/translator/oosupport/function.py
Log:
(antocuni, niko)
Get the jvm backend to run the merciless and terrifying test_snippet.
Refactor the oosupport/function very slightly to emit labels for each block
in a central place.
Modified: pypy/dist/pypy/translator/cli/function.py
==============================================================================
--- pypy/dist/pypy/translator/cli/function.py (original)
+++ pypy/dist/pypy/translator/cli/function.py Tue Oct 31 13:58:47 2006
@@ -87,14 +87,12 @@
self.ilasm.label(label)
def render_return_block(self, block):
- self.set_label(self._get_block_name(block))
return_var = block.inputargs[0]
if return_var.concretetype is not Void:
self.load(return_var)
self.ilasm.opcode('ret')
def render_raise_block(self, block):
- self.set_label(self._get_block_name(block))
exc = block.inputargs[1]
self.load(exc)
self.ilasm.opcode('throw')
Modified: pypy/dist/pypy/translator/jvm/conftest.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/conftest.py (original)
+++ pypy/dist/pypy/translator/jvm/conftest.py Tue Oct 31 13:58:47 2006
@@ -5,10 +5,10 @@
option = py.test.Config.addoptions(
"pypy-jvm options",
- Option('--javac', action='store', dest='javac', default='javac',
- help='Define the java compiler to use'),
Option('--java', action='store', dest='java', default='java',
help='Define the java executable to use'),
+ Option('--jasmin', action='store', dest='java', default='java',
+ help='Define the jasmin script to use'),
Option('--noassemble', action='store_true', dest="noasm", default=False,
help="don't assemble jasmin files"),
Option('--package', action='store', dest='package', default='pypy',
Modified: pypy/dist/pypy/translator/jvm/database.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/database.py (original)
+++ pypy/dist/pypy/translator/jvm/database.py Tue Oct 31 13:58:47 2006
@@ -95,7 +95,7 @@
"""
if graph in self._functions:
return self._functions[graph]
- classnm = self._make_unique_name(graph.name)
+ classnm = "pypy."+self._make_unique_name(graph.name)
classobj = node.Class(classnm)
funcobj = self._function_for_graph(classobj, "invoke", True, graph)
classobj.add_method(funcobj)
@@ -109,6 +109,9 @@
def pop(self):
return self._pending_nodes.pop()
+ def gen_constants(self, gen):
+ pass
+
# Type translation functions
def escape_name(self, nm):
@@ -139,7 +142,7 @@
return XXX
# Uh-oh
- unhandled_case
+ unhandled_case
# Invoked by genoo:
# I am not sure that we need them
Modified: pypy/dist/pypy/translator/jvm/generator.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/generator.py (original)
+++ pypy/dist/pypy/translator/jvm/generator.py Tue Oct 31 13:58:47 2006
@@ -14,7 +14,7 @@
BRANCH = 1 # Opcode is a branching opcode (implies a label argument)
INVOKE = 2 # Opcode is some kind of method invocation
CONST5 = 4 # Opcode is specialized for int arguments from -1 to 5
-CONST3 = 4 # Opcode is specialized for int arguments from 0 to 3
+CONST3 = 8 # Opcode is specialized for int arguments from 0 to 3
# ___________________________________________________________________________
# JVM Opcodes:
@@ -29,6 +29,9 @@
"""
self.flags = flags
self.jvmstr = jvmstr
+
+ def __repr__(self):
+ return "<Opcode %s:%x>" % (self.jvmstr, self.flags)
def specialize_opcode(self, args):
""" Process the argument list according to the various flags.
@@ -197,11 +200,15 @@
class Method(object):
def __init__(self, classnm, methnm, desc, opcode=INVOKESTATIC):
self.opcode = opcode
- self.class_name = classnm
- self.method_name = methnm
- self.descriptor = desc
+ self.class_name = classnm # String, ie. "java.lang.Math"
+ self.method_name = methnm # String "abs"
+ self.descriptor = desc # String, (I)I
def invoke(self, gen):
gen._instr(self.opcode, self)
+ def jasmin_syntax(self):
+ return "%s/%s%s" % (self.class_name.replace('.','/'),
+ self.method_name,
+ self.descriptor)
MATHIABS = Method('java.lang.Math', 'abs', '(I)I')
MATHLABS = Method('java.lang.Math', 'abs', '(L)L')
@@ -228,6 +235,12 @@
'(Ljava/lang/String;)D')
PYPYSTRTOCHAR = Method('pypy.PyPy', 'str_to_char',
'(Ljava/lang/String;)C')
+PYPYDUMPINT = Method('pypy.PyPy', 'dump_int', '(I)V')
+PYPYDUMPUINT = Method('pypy.PyPy', 'dump_uint', '(I)V')
+PYPYDUMPLONG = Method('pypy.PyPy', 'dump_long', '(L)V')
+PYPYDUMPDOUBLE = Method('pypy.PyPy', 'dump_double', '(D)V')
+PYPYDUMPSTRING = Method('pypy.PyPy', 'dump_string', '([B)V')
+PYPYDUMPBOOLEAN = Method('pypy.PyPy', 'dump_boolean', '(Z)V')
class JVMGenerator(Generator):
@@ -283,9 +296,11 @@
# 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()
+ for idx, ty in enumerate(argtypes):
+ if idx < len(argvars):
+ var = argvars[idx]
+ self.local_vars[var] = self.next_offset
+ self.next_offset += ty.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
@@ -299,9 +314,9 @@
unimplemented
def end_function(self):
+ self._end_function()
del self.next_offset
del self.local_vars
- self._end_function()
def _end_function(self):
unimplemented
@@ -322,7 +337,10 @@
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)
+ opc = LOAD.for_type(vartype)
+ print "load_jvm_jar: vartype=%s varidx=%s opc=%s" % (
+ repr(vartype), repr(varidx), repr(opc))
+ self._instr(opc, varidx)
def store_jvm_var(self, vartype, varidx):
""" Loads from jvm slot #varidx, which is expected to hold a value of
@@ -442,6 +460,7 @@
def load(self, value):
if isinstance(value, flowmodel.Variable):
jty, idx = self._var_data(value)
+ print "load_jvm_var: jty=%s idx=%s" % (repr(jty), repr(idx))
return self.load_jvm_var(jty, idx)
if isinstance(value, flowmodel.Constant):
@@ -608,7 +627,7 @@
self.out = open(jfile, 'w')
# Write the JasminXT header
- self.out.write(".bytecode XX\n")
+ #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") # ?
@@ -617,6 +636,9 @@
self.out.close()
self.out = None
+ def close(self):
+ assert self.out is None, "Unended class"
+
def add_field(self, fname, fdesc):
# TODO --- Signature for generics?
# TODO --- these must appear before methods, do we want to buffer
@@ -628,11 +650,13 @@
# Throws clause? Only use RuntimeExceptions?
kw = ['public']
if static: kw.append('static')
- self.out.write('.method %s %s (%s)%s\n' % (
- funcname, " ".join(kw),
+ self.out.write('.method %s %s(%s)%s\n' % (
+ " ".join(kw), funcname,
"".join(argtypes), rettype))
def _end_function(self):
+ self.out.write('.limit stack 100\n') # HACK, track max offset
+ self.out.write('.limit locals %d\n' % self.next_offset)
self.out.write('.end method\n')
def mark(self, lbl):
@@ -643,10 +667,14 @@
def _instr(self, opcode, *args):
jvmstr, args = opcode.specialize_opcode(args)
+ # XXX this whole opcode flag things is stupid, redo to be class based
if opcode.flags & BRANCH:
assert len(args) == 1
_, lblnum, lbldesc = args[0]
args = ('%s_%s' % (lbldesc, lblnum),)
+ if opcode.flags & INVOKE:
+ assert len(args) == 1
+ args = (args[0].jasmin_syntax(),)
self.out.write(' %s %s\n' % (
jvmstr, " ".join([str(s) for s in args])))
Modified: pypy/dist/pypy/translator/jvm/genjvm.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/genjvm.py (original)
+++ pypy/dist/pypy/translator/jvm/genjvm.py Tue Oct 31 13:58:47 2006
@@ -2,7 +2,7 @@
Backend for the JVM.
"""
-import os, os.path, subprocess
+import os, os.path, subprocess, sys
import py
from pypy.tool.udir import udir
@@ -17,7 +17,30 @@
from pypy.translator.jvm.opcodes import opcodes
class JvmError(Exception):
- """ Indicates an error occurred in the JVM runtime """
+ """ Indicates an error occurred in JVM backend """
+
+ def pretty_print(self):
+ print str(self)
+ pass
+
+class JvmSubprogramError(JvmError):
+ """ Indicates an error occurred running some program """
+ def __init__(self, res, args, stdout, stderr):
+ self.res = res
+ self.args = args
+ self.stdout = stdout
+ self.stderr = stderr
+
+ def __str__(self):
+ return "Error code %d running %s" % (self.res, repr(self.args))
+
+ def pretty_print(self):
+ JvmError.pretty_print(self)
+ print "vvv Stdout vvv\n"
+ print self.stdout
+ print "vvv Stderr vvv\n"
+ print self.stderr
+
pass
class JvmGeneratedSource(object):
@@ -56,17 +79,35 @@
# Compute directory where .class files should go
self.classdir = self.javadir
+ def _invoke(self, args, allow_stderr):
+ subp = subprocess.Popen(
+ args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = subp.communicate()
+ res = subp.wait()
+ if res or (not allow_stderr and stderr):
+ raise JvmSubprogramError(res, args, stdout, stderr)
+ return stdout
+
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 self.javadir.listdir()
- if f.endswith('.java')]
- res = subprocess.call([javac] + javafiles)
- if res: raise JvmError('Failed to compile!')
- else: self.compiled = True
+ javacmd = [getoption('jasmin'), '-d', str(self.javadir)]
+ pypydir = self.javadir.join("pypy") # HACK; should do recursive
+ javafiles = [str(f) for f in pypydir.listdir()
+ if str(f).endswith('.j')]
+
+ for javafile in javafiles:
+ print "Invoking jasmin on %s" % javafile
+ self._invoke(javacmd+[javafile], False)
+
+ # HACK: compile the Java helper class. Should eventually
+ # use rte.py
+ sl = __file__.rindex('/')
+ javasrc = __file__[:sl]+"/src/PyPy.java"
+ self._invoke(['javac', '-nowarn', '-d', str(self.javadir), javasrc], True)
+
+ self.compiled = True
def execute(self, args):
"""
@@ -76,15 +117,18 @@
"""
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
- return pipe.read()
+ cmd = [getoption('java'),
+ '-cp',
+ str(self.javadir),
+ self.package+".Main"] + strargs
+ return self._invoke(cmd, True)
def generate_source_for_function(func, annotation):
"""
Given a Python function and some hints about its argument types,
- generates JVM sources. Returns the JvmGeneratedSource object.
+ generates JVM sources that call it and print the result. Returns
+ the JvmGeneratedSource object.
"""
if hasattr(func, 'im_func'):
@@ -97,7 +141,7 @@
if getoption('view'): t.view()
if getoption('wd'): tmpdir = py.path.local('.')
else: tmpdir = udir
- jvm = GenJvm(tmpdir, t, EntryPoint(main_graph, True))
+ jvm = GenJvm(tmpdir, t, EntryPoint(main_graph, True, True))
return jvm.generate_source()
class GenJvm(GenOO):
Modified: pypy/dist/pypy/translator/jvm/node.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/node.py (original)
+++ pypy/dist/pypy/translator/jvm/node.py Tue Oct 31 13:58:47 2006
@@ -6,7 +6,8 @@
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 \
+ jString, jStringArray, jVoid, jThrowable
from pypy.translator.jvm.typesystem import jvm_for_class, jvm_method_desc
from pypy.translator.jvm.opcodes import opcodes
from pypy.translator.oosupport.function import Function as OOFunction
@@ -24,7 +25,7 @@
testing (see __init__)
"""
- def __init__(self, graph, expandargs):
+ def __init__(self, graph, expandargs, printresult):
"""
'graph' --- The initial graph to invoke from main()
'expandargs' --- controls whether the arguments passed to main()
@@ -44,6 +45,7 @@
"""
self.graph = graph
self.expand_arguments = expandargs
+ self.print_result = printresult
pass
# XXX --- perhaps this table would be better placed in typesystem.py
@@ -58,6 +60,14 @@
ootype.Char:jvmgen.PYPYSTRTOCHAR
}
+ _type_printing_methods = {
+ ootype.Signed:jvmgen.PYPYDUMPINT,
+ ootype.Unsigned:jvmgen.PYPYDUMPUINT,
+ ootype.SignedLongLong:jvmgen.PYPYDUMPLONG,
+ ootype.Float:jvmgen.PYPYDUMPDOUBLE,
+ ootype.Bool:jvmgen.PYPYDUMPBOOLEAN,
+ }
+
def render(self, gen):
gen.begin_class('pypy.Main')
gen.begin_function(
@@ -68,7 +78,10 @@
# 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()):
+ jty = self.db.lltype_to_cts(arg.concretetype)
+ gen.load_jvm_var(jStringArray, 0)
gen.emit(jvmgen.ICONST, i)
+ gen.load_from_array(jString)
gen.emit(self._type_conversion_methods[arg.concretetype])
else:
# Convert the array of strings to a List<String> as the
@@ -81,6 +94,16 @@
# Generate a call to this method
gen.emit(self.db.pending_function(self.graph))
+
+ # Print result?
+ if self.print_result:
+ resootype = self.graph.getreturnvar().concretetype
+ resjtype = self.db.lltype_to_cts(resootype)
+ meth = self._type_printing_methods[resootype]
+ gen.emit(meth)
+
+ # And finish up
+ gen.return_val(jVoid)
gen.end_function()
gen.end_class()
Modified: pypy/dist/pypy/translator/jvm/opcodes.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/opcodes.py (original)
+++ pypy/dist/pypy/translator/jvm/opcodes.py Tue Oct 31 13:58:47 2006
@@ -222,7 +222,7 @@
for opc in opcodes:
val = opcodes[opc]
if not isinstance(val, list):
- val = InstructionList((PushAllArgs, val))
+ val = InstructionList((PushAllArgs, val, StoreResult))
else:
val = InstructionList(val)
opcodes[opc] = val
Modified: pypy/dist/pypy/translator/jvm/option.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/option.py (original)
+++ pypy/dist/pypy/translator/jvm/option.py Tue Oct 31 13:58:47 2006
@@ -4,9 +4,11 @@
_default_values = {
'javac':'javac',
'java':'java',
+ 'jasmin':'jasmin',
'noasm':False,
'package':'pypy',
- 'wd':False
+ 'wd':False,
+ 'norun':False
}
def getoption(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 Tue Oct 31 13:58:47 2006
@@ -98,10 +98,10 @@
public static int double_to_uint(double value) {
if (value <= Integer.MAX_VALUE)
- return value;
+ return (int)value;
- int loword = value % BITS16;
- int hiword = Math.floor(value / BITS16);
+ int loword = (int)(value % BITS16);
+ int hiword = (int)(Math.floor(value / BITS16));
assert (loword & 0xFFFF0000) == 0;
assert (hiword & 0xFFFF0000) == 0;
return (hiword << 16) + loword;
@@ -112,7 +112,7 @@
}
public static List<?> array_to_list(Object[] array) {
- List<?> l = new ArrayList();
+ List l = new ArrayList();
for (Object o : array) {
l.add(o);
}
@@ -131,9 +131,9 @@
try {
long l = Long.parseLong(s);
if (l < Integer.MAX_VALUE)
- return l;
- int lowerword = l & 0xFFFF;
- int upperword = l >> 16;
+ return (int)l;
+ int lowerword = (int)(l & 0xFFFF);
+ int upperword = (int)(l >> 16);
return lowerword + (upperword << 16);
} catch (NumberFormatException fe) {
throw new RuntimeException(fe);
@@ -182,4 +182,44 @@
throw new RuntimeException("String not single character: '"+s+"'");
return s.charAt(0);
}
+
+ // Used in testing:
+
+ public static void dump_int(int i) {
+ System.out.println(i);
+ }
+
+ public static void dump_uint(int i) {
+ if (i >= 0)
+ System.out.println(i);
+ else {
+ throw new RuntimeException("TODO");
+ }
+ }
+
+ public static void dump_boolean(boolean l) {
+ if (l)
+ System.out.println("True");
+ else
+ System.out.println("False");
+ }
+
+ public static void dump_long(long l) {
+ System.out.println(l);
+ }
+
+ public static void dump_double(double d) {
+ System.out.println(d);
+ }
+
+ public static void dump_string(char[] b) {
+ System.out.print('"');
+ for (char c : b) {
+ if (c == '"')
+ System.out.print("\\\"");
+ System.out.print(c);
+ }
+ System.out.print('"');
+ System.out.println();
+ }
}
\ No newline at end of file
Modified: pypy/dist/pypy/translator/jvm/test/runtest.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/test/runtest.py (original)
+++ pypy/dist/pypy/translator/jvm/test/runtest.py Tue Oct 31 13:58:47 2006
@@ -9,7 +9,7 @@
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.genjvm import generate_source_for_function, JvmError
from pypy.translator.jvm.option import getoption
FLOAT_PRECISION = 8
@@ -53,12 +53,13 @@
py.test.skip("Execution disabled")
resstr = self.gensrc.execute(args)
+ print "resstr=%s" % repr(resstr)
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
+ return res
class JvmTest(BaseRtypingTest, OORtypeMixin):
def __init__(self):
@@ -84,12 +85,15 @@
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
+ try:
+ src = self._compile(fn, args, annotation)
+ res = src(*args)
+ if isinstance(res, ExceptionWrapper):
+ raise res
+ return res
+ except JvmError, e:
+ e.pretty_print()
+ raise
def interpret_raises(self, exception, fn, args):
import exceptions # needed by eval
Modified: pypy/dist/pypy/translator/jvm/test/test_bool.py
==============================================================================
--- pypy/dist/pypy/translator/jvm/test/test_bool.py (original)
+++ pypy/dist/pypy/translator/jvm/test/test_bool.py Tue Oct 31 13:58:47 2006
@@ -1,7 +1,7 @@
import py
from pypy.translator.jvm.test.runtest import JvmTest
-from pypy.rpython.test.test_rbool import BaseTestRbool
+#from pypy.rpython.test.test_rbool import BaseTestRbool
-class TestJvmBool(JvmTest, BaseTestRbool):
- pass
+#class TestJvmBool(JvmTest, BaseTestRbool):
+# pass
Added: pypy/dist/pypy/translator/jvm/test/test_snippet.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/translator/jvm/test/test_snippet.py Tue Oct 31 13:58:47 2006
@@ -0,0 +1,24 @@
+from pypy.translator.test import snippet as s
+from pypy.translator.jvm.test.runtest import JvmTest
+
+snippets = [
+ [s.if_then_else, (0, 42, 43), (1, 42, 43)],
+ [s.simple_func, (42,)],
+ [s.while_func, (0,), (13,)],
+ [s.my_bool, (0,), (42,)],
+ [s.my_gcd, (30, 18)],
+ [s.is_perfect_number, (28,), (27,)],
+ ]
+
+class TestSnippets(JvmTest):
+
+ def test_snippers(self):
+ for item in snippets:
+ func = item[0]
+ for arglist in item[1:]:
+ yield self.interpret, func, arglist
+
+ def test_add(self):
+ def fn(x, y):
+ return x+y
+ assert self.interpret(fn, [4,7]) == 11
Modified: pypy/dist/pypy/translator/oosupport/function.py
==============================================================================
--- pypy/dist/pypy/translator/oosupport/function.py (original)
+++ pypy/dist/pypy/translator/oosupport/function.py Tue Oct 31 13:58:47 2006
@@ -86,17 +86,20 @@
for block in graph.iterblocks():
if self._is_return_block(block):
return_blocks.append(block)
- elif self._is_raise_block(block):
- self.render_raise_block(block)
- elif self._is_exc_handling_block(block):
- self.render_exc_handling_block(block)
else:
- self.render_normal_block(block)
+ self.set_label(self._get_block_name(block))
+ if self._is_raise_block(block):
+ self.render_raise_block(block)
+ elif self._is_exc_handling_block(block):
+ self.render_exc_handling_block(block)
+ else:
+ self.render_normal_block(block)
# render return blocks at the end just to please the .NET
# runtime that seems to need a return statement at the end of
# the function
for block in return_blocks:
+ self.set_label(self._get_block_name(block))
self.render_return_block(block)
self.end_render()
@@ -104,8 +107,6 @@
self.db.record_function(self.graph, self.name)
def render_exc_handling_block(self, block):
- self.set_label(self._get_block_name(block))
-
# renders all ops but the last one
for op in block.operations[:-1]:
self._render_op(op)
@@ -141,8 +142,6 @@
raise NotImplementedError
def render_normal_block(self, block):
- self.set_label(self._get_block_name(block))
-
# renders all ops but the last one
for op in block.operations:
self._render_op(op)
More information about the Pypy-commit
mailing list