Author: Carl Friedrich Bolz-Tereick <[email protected]>
Branch: py3.7
Changeset: r98552:f20ec1e0843b
Date: 2020-01-17 21:31 +0100
http://bitbucket.org/pypy/pypy/changeset/f20ec1e0843b/

Log:    merge py3.7-call-changes

        implement more faithfully the bytecodes that CPython 3.7 uses for
        calling functions

diff --git a/lib-python/3/opcode.py b/lib-python/3/opcode.py
--- a/lib-python/3/opcode.py
+++ b/lib-python/3/opcode.py
@@ -31,12 +31,10 @@
 haslocal = []
 hascompare = []
 hasfree = []
-hasnargs = []
+hasnargs = [] # unused
 
 opmap = {}
-opname = [''] * 256
-for op in range(256): opname[op] = '<%r>' % (op,)
-del op
+opname = ['<%r>' % (op,) for op in range(256)]
 
 def def_op(name, op):
     opname[op] = name
@@ -174,11 +172,10 @@
 name_op('STORE_ANNOTATION', 127) # Index in name list XXX: removed in CPython 
3.7
 
 def_op('RAISE_VARARGS', 130)    # Number of raise arguments (1, 2, or 3)
-def_op('CALL_FUNCTION', 131)    # #args + (#kwargs << 8)
-hasnargs.append(131)
-def_op('MAKE_FUNCTION', 132)    # Number of args with default values
+def_op('CALL_FUNCTION', 131)    # #args
+
+def_op('MAKE_FUNCTION', 132)    # Flags
 def_op('BUILD_SLICE', 133)      # Number of items
-def_op('MAKE_CLOSURE', 134)
 def_op('LOAD_CLOSURE', 135)
 hasfree.append(135)
 def_op('LOAD_DEREF', 136)
@@ -188,12 +185,9 @@
 def_op('DELETE_DEREF', 138)
 hasfree.append(138)
 
-def_op('CALL_FUNCTION_VAR', 140)     # #args + (#kwargs << 8)
-hasnargs.append(140)
-def_op('CALL_FUNCTION_KW', 141)      # #args + (#kwargs << 8)
-hasnargs.append(141)
-def_op('CALL_FUNCTION_VAR_KW', 142)  # #args + (#kwargs << 8)
-hasnargs.append(142)
+def_op('CALL_FUNCTION_KW', 141)  # #args + #kwargs
+
+def_op('CALL_FUNCTION_EX', 142)  # Flags
 
 jrel_op('SETUP_WITH', 143)
 
@@ -204,7 +198,6 @@
 def_op('LOAD_CLASSDEREF', 148)
 hasfree.append(148)
 
-jrel_op('SETUP_ASYNC_WITH', 154)
 
 def_op('EXTENDED_ARG', 144)
 EXTENDED_ARG = 144
@@ -215,18 +208,20 @@
 def_op('BUILD_TUPLE_UNPACK', 152)
 def_op('BUILD_SET_UNPACK', 153)
 
-def_op('FORMAT_VALUE', 155)   # in CPython 3.6, but available in PyPy from 3.5
+jrel_op('SETUP_ASYNC_WITH', 154)
+
+def_op('FORMAT_VALUE', 155)
 def_op('BUILD_CONST_KEY_MAP', 156)
-def_op('BUILD_STRING', 157)   # in CPython 3.6, but available in PyPy from 3.5
+def_op('BUILD_STRING', 157)
 
 #name_op('LOAD_METHOD', 160)
-#def_op('CALL_METHOD', 161)
+def_op('CALL_METHOD', 161)
 
 # pypy modification, experimental bytecode
 def_op('LOOKUP_METHOD', 201)          # Index in name list
 hasname.append(201)
-def_op('CALL_METHOD', 202)            # #args not including 'self'
 def_op('BUILD_LIST_FROM_ARG', 203)
+def_op('CALL_METHOD_KW', 204)
 def_op('LOAD_REVDB_VAR', 205)         # reverse debugger (syntax example: $5)
 
 del def_op, name_op, jrel_op, jabs_op
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -16,3 +16,8 @@
 .. branch: bpo-16055
 
 Fixes incorrect error text for ``int('1', base=1000)``
+
+.. branch py3.7-call-changes
+
+Implement the CPython 3.7 changes to the call bytecodes, including supporting
+more than 255 arguments.
diff --git a/pypy/interpreter/astcompiler/assemble.py 
b/pypy/interpreter/astcompiler/assemble.py
--- a/pypy/interpreter/astcompiler/assemble.py
+++ b/pypy/interpreter/astcompiler/assemble.py
@@ -768,9 +768,6 @@
 def _compute_BUILD_MAP_UNPACK_WITH_CALL(arg):
     return 1 - (arg & 0xFF)
 
-def _compute_MAKE_CLOSURE(arg):
-    return -2 - _num_args(arg) - ((arg >> 16) & 0xFFFF)
-
 def _compute_MAKE_FUNCTION(arg):
     return -1 - bool(arg & 0x01) - bool(arg & 0x02) - bool(arg & 0x04) - 
bool(arg & 0x08)
 
@@ -783,23 +780,22 @@
 def _compute_RAISE_VARARGS(arg):
     return -arg
 
-def _num_args(oparg):
-    return (oparg % 256) + 2 * ((oparg // 256) % 256)
-
 def _compute_CALL_FUNCTION(arg):
-    return -_num_args(arg)
-
-def _compute_CALL_FUNCTION_VAR(arg):
-    return -_num_args(arg) - 1
+    return -arg
 
 def _compute_CALL_FUNCTION_KW(arg):
-    return -_num_args(arg) - 1
+    return -arg - 1
 
-def _compute_CALL_FUNCTION_VAR_KW(arg):
-    return -_num_args(arg) - 2
+def _compute_CALL_FUNCTION_EX(arg):
+    assert arg == 0 or arg == 1
+    # either -1 or -2
+    return -arg - 1
 
 def _compute_CALL_METHOD(arg):
-    return -_num_args(arg) - 1
+    return -arg - 1
+
+def _compute_CALL_METHOD_KW(arg):
+    return -arg - 2
 
 def _compute_FORMAT_VALUE(arg):
     if (arg & consts.FVS_MASK) == consts.FVS_HAVE_SPEC:
diff --git a/pypy/interpreter/astcompiler/astbuilder.py 
b/pypy/interpreter/astcompiler/astbuilder.py
--- a/pypy/interpreter/astcompiler/astbuilder.py
+++ b/pypy/interpreter/astcompiler/astbuilder.py
@@ -598,8 +598,6 @@
         kwdefaults = []
         kwarg = None
         vararg = None
-        if n_pos + n_kwdonly > 255:
-            self.error("more than 255 arguments", arguments_node)
         # process args
         i = 0
         have_default = False
@@ -1113,8 +1111,6 @@
                 (generator_count and (keyword_count or arg_count)):
             self.error("Generator expression must be parenthesized "
                        "if not sole argument", args_node)
-        if arg_count + keyword_count + generator_count > 255:
-            self.error("more than 255 arguments", args_node)
         args = []
         keywords = []
         used_keywords = {}
diff --git a/pypy/interpreter/astcompiler/codegen.py 
b/pypy/interpreter/astcompiler/codegen.py
--- a/pypy/interpreter/astcompiler/codegen.py
+++ b/pypy/interpreter/astcompiler/codegen.py
@@ -1412,99 +1412,20 @@
             self.load_const(self.space.newtext(keyword.arg))
         keyword.value.walkabout(self)
 
-    def _make_call(self, n, # args already pushed
-                   args, keywords):
-        call_type = 0
-        # the number of tuples and dictionaries on the stack
-        nsubargs = 0
-        nsubkwargs = 0
-        nkw = 0
-        nseen = 0 # the number of positional arguments on the stack
-        if args is not None:
-            for elt in args:
-                if isinstance(elt, ast.Starred):
-                    # A star-arg. If we've seen positional arguments,
-                    # pack the positional arguments into a tuple.
-                    if nseen:
-                        self.emit_op_arg(ops.BUILD_TUPLE, nseen)
-                        nseen = 0
-                        nsubargs += 1
-                    elt.value.walkabout(self)
-                    nsubargs += 1
-                elif nsubargs:
-                    # We've seen star-args already, so we
-                    # count towards items-to-pack-into-tuple.
-                    elt.walkabout(self)
-                    nseen += 1
-                else:
-                    # Positional arguments before star-arguments
-                    # are left on the stack.
-                    elt.walkabout(self)
-                    n += 1
-            if nseen:
-                # Pack up any trailing positional arguments.
-                self.emit_op_arg(ops.BUILD_TUPLE, nseen)
-                nsubargs += 1
-            if nsubargs:
-                call_type |= 1
-                if nsubargs > 1:
-                    # If we ended up with more than one stararg, we need
-                    # to concatenate them into a single sequence.
-                    self.emit_op_arg(ops.BUILD_LIST_UNPACK, nsubargs)
+    def _load_constant_tuple(self, content_w):
+        self.load_const(self.space.newtuple(content_w[:]))
 
-        # Repeat procedure for keyword args
-        nseen = 0 # the number of keyword arguments on the stack following
-        if keywords is not None:
-            for kw in keywords:
-                assert isinstance(kw, ast.keyword)
-                if kw.arg is None:
-                    # A keyword argument unpacking.
-                    if nseen:
-                        self.emit_op_arg(ops.BUILD_MAP, nseen)
-                        nseen = 0
-                        nsubkwargs += 1
-                    kw.value.walkabout(self)
-                    nsubkwargs += 1
-                elif nsubkwargs:
-                    # A keyword argument and we already have a dict.
-                    self.load_const(self.space.newtext(kw.arg))
-                    kw.value.walkabout(self)
-                    nseen += 1
-                else:
-                    # keyword argument
-                    kw.walkabout(self)
-                    nkw += 1
-            if nseen:
-                # Pack up any trailing keyword arguments.
-                self.emit_op_arg(ops.BUILD_MAP,nseen)
-                nsubkwargs += 1
-            if nsubkwargs:
-                call_type |= 2
-                if nsubkwargs > 1:
-                    # Pack it all up
-                    function_pos = n + (call_type & 1) + nkw + 1
-                    self.emit_op_arg(ops.BUILD_MAP_UNPACK_WITH_CALL, 
(nsubkwargs | (function_pos << 8)))
-
-        assert n < 1<<8
-        assert nkw < 1<<24
-        n |= nkw << 8;
-
-        op = 0
-        if call_type == 0:
-            op = ops.CALL_FUNCTION
-        elif call_type == 1:
-            op = ops.CALL_FUNCTION_VAR
-        elif call_type == 2:
-            op = ops.CALL_FUNCTION_KW
-        elif call_type == 3:
-            op = ops.CALL_FUNCTION_VAR_KW
-        self.emit_op_arg(op, n)
+    def _make_call(self, nargs_pushed, args, keywords):
+        space = self.space
+        CallCodeGenerator(self, nargs_pushed, args, keywords).emit_call()
 
     def visit_Call(self, call):
         self.update_position(call.lineno)
         if self._optimize_method_call(call):
             return
         call.func.walkabout(self)
+        #if getattr(call.func, "id", None) == "f":
+        #    import pdb; pdb.set_trace()
         self._make_call(0, call.args, call.keywords)
 
     def _call_has_no_star_args(self, call):
@@ -1523,6 +1444,7 @@
         return self._call_has_no_star_args(call) and not call.keywords
 
     def _optimize_method_call(self, call):
+        space = self.space
         if not self._call_has_no_star_args(call) or \
            not isinstance(call.func, ast.Attribute):
             return False
@@ -1532,9 +1454,18 @@
         self.emit_op_name(ops.LOOKUP_METHOD, self.names, attr_lookup.attr)
         self.visit_sequence(call.args)
         arg_count = len(call.args) if call.args is not None else 0
-        self.visit_sequence(call.keywords)
-        kwarg_count = len(call.keywords) if call.keywords is not None else 0
-        self.emit_op_arg(ops.CALL_METHOD, (kwarg_count << 8) | arg_count)
+        if not call.keywords:
+            self.emit_op_arg(ops.CALL_METHOD, arg_count)
+        else:
+            keyword_names_w = []
+            for kw in call.keywords:
+                assert isinstance(kw, ast.keyword)
+                assert kw.arg  # checked by self._call_has_no_star_args
+                w_name = space.newtext(kw.arg)
+                keyword_names_w.append(misc.intern_if_common_string(space, 
w_name))
+                kw.value.walkabout(self)
+            self._load_constant_tuple(keyword_names_w)
+            self.emit_op_arg(ops.CALL_METHOD_KW, len(keyword_names_w) + 
arg_count)
         return True
 
     def visit_ListComp(self, lc):
@@ -1946,3 +1877,127 @@
         if self.scope.doc_removable:
             flags |= consts.CO_KILL_DOCSTRING
         return PythonCodeGenerator._get_code_flags(self) | flags
+
+
+class CallCodeGenerator(object):
+    def __init__(self, codegenerator, nargs_pushed, args, keywords):
+        self.space = codegenerator.space
+        self.codegenerator = codegenerator
+        self.nargs_pushed = nargs_pushed
+        self.args = args
+        self.keywords = keywords
+
+        # the number of tuples and dictionaries on the stack
+        self.nsubargs = 0
+        self.nsubkwargs = 0
+        self.keyword_names_w = []
+
+    def _pack_positional_into_tuple(self):
+        if self.nargs_pushed:
+            self.codegenerator.emit_op_arg(ops.BUILD_TUPLE, self.nargs_pushed)
+            self.nsubargs += 1
+            self.nargs_pushed = 0
+
+    def _push_args(self):
+        for elt in self.args:
+            if isinstance(elt, ast.Starred):
+                # we have a *arg
+                self._pack_positional_into_tuple()
+                elt.value.walkabout(self.codegenerator)
+                self.nsubargs += 1
+                continue
+            if self.nargs_pushed >= MAX_STACKDEPTH_CONTAINERS // 2:
+                # stack depth getting too big
+                self._pack_positional_into_tuple()
+            elt.walkabout(self.codegenerator)
+            self.nargs_pushed += 1
+        if self.nsubargs:
+            # Pack up any trailing positional arguments.
+            self._pack_positional_into_tuple()
+            if self.nsubargs > 1:
+                # If we ended up with more than one stararg, we need
+                # to concatenate them into a single sequence.
+                # XXX CPython uses BUILD_TUPLE_UNPACK_WITH_CALL, but I
+                # don't quite see the difference?
+                self.codegenerator.emit_op_arg(ops.BUILD_TUPLE_UNPACK, 
self.nsubargs)
+
+    def _pack_kwargs_into_dict(self):
+        if self.keyword_names_w:
+            self.codegenerator._load_constant_tuple(self.keyword_names_w)
+            # XXX use BUILD_MAP for size 1?
+            self.codegenerator.emit_op_arg(ops.BUILD_CONST_KEY_MAP, 
len(self.keyword_names_w))
+            self.keyword_names_w = []
+            self.nsubkwargs += 1
+
+    def _push_kwargs(self):
+        for kw in self.keywords:
+            assert isinstance(kw, ast.keyword)
+            if kw.arg is None:
+                # if we see **args or if the number of keywords is huge,
+                # pack up keywords on the stack so far
+                self._pack_kwargs_into_dict()
+                kw.value.walkabout(self.codegenerator)
+                self.nsubkwargs += 1
+                continue
+            if len(self.keyword_names_w) > MAX_STACKDEPTH_CONTAINERS // 2:
+                self._pack_kwargs_into_dict()
+            w_name = self.space.newtext(kw.arg)
+            
self.keyword_names_w.append(misc.intern_if_common_string(self.space, w_name))
+            kw.value.walkabout(self.codegenerator)
+        if self.nsubkwargs:
+            self._pack_kwargs_into_dict()
+            if self.nsubkwargs > 1:
+                # Pack it all up
+                self.codegenerator.emit_op_arg(ops.BUILD_MAP_UNPACK_WITH_CALL, 
self.nsubkwargs)
+
+    def _pack_positional_args_into_tuple(self):
+        if self.nargs_pushed == 0:
+            self.codegenerator._load_constant_tuple([])
+        else:
+            self.codegenerator.emit_op_arg(ops.BUILD_TUPLE, self.nargs_pushed)
+        self.nsubargs += 1
+
+    def _push_tuple_positional_args_if_necessary(self):
+        if self.nsubargs:
+            # can't use CALL_FUNCTION_KW anyway, because we already have a
+            # tuple as the positional args
+            return
+        # we might get away with using CALL_FUNCTION_KW if there are no 
**kwargs
+        for kw in self.keywords:
+            assert isinstance(kw, ast.keyword)
+            if kw.arg is None:
+                # we found a **kwarg, thus we're using CALL_FUNCTION_EX, we
+                # need to pack up positional arguments first
+                self._pack_positional_args_into_tuple()
+                break
+        if self.nsubargs == 0 and len(self.keywords) > 
MAX_STACKDEPTH_CONTAINERS // 2:
+            # we have a huge amount of keyword args, thus we also need to use
+            # CALL_FUNCTION_EX
+            self._pack_positional_args_into_tuple()
+
+    def emit_call(self):
+        keywords = self.keywords
+        codegenerator = self.codegenerator
+        space = self.space
+        if self.args is not None:
+            self._push_args()
+
+        # Repeat procedure for keyword args
+        if keywords is None or len(keywords) == 0:
+            if not self.nsubargs:
+                # no *args, no keyword args, no **kwargs
+                codegenerator.emit_op_arg(ops.CALL_FUNCTION, self.nargs_pushed)
+                return
+        else:
+            self._push_tuple_positional_args_if_necessary()
+            self._push_kwargs()
+
+        if self.nsubkwargs == 0 and self.nsubargs == 0:
+            # can use CALL_FUNCTION_KW
+            assert len(self.keyword_names_w) > 0 # otherwise we would have 
used CALL_FUNCTION
+            codegenerator._load_constant_tuple(self.keyword_names_w)
+            codegenerator.emit_op_arg(ops.CALL_FUNCTION_KW, self.nargs_pushed 
+ len(self.keyword_names_w))
+        else:
+            self._pack_kwargs_into_dict()
+            codegenerator.emit_op_arg(ops.CALL_FUNCTION_EX, 
int(self.nsubkwargs > 0))
+
diff --git a/pypy/interpreter/astcompiler/test/test_astbuilder.py 
b/pypy/interpreter/astcompiler/test/test_astbuilder.py
--- a/pypy/interpreter/astcompiler/test/test_astbuilder.py
+++ b/pypy/interpreter/astcompiler/test/test_astbuilder.py
@@ -570,13 +570,13 @@
         for i in range(255):
             fundef += "i%d, "%i
         fundef += "*, key=100):\n pass\n"
-        pytest.raises(SyntaxError, self.get_first_stmt, fundef)
+        self.get_first_stmt(fundef) # no crash, works since 3.7
 
         fundef2 = "def foo(i,*,"
         for i in range(255):
             fundef2 += "i%d, "%i
         fundef2 += "lastarg):\n  pass\n"
-        pytest.raises(SyntaxError, self.get_first_stmt, fundef)
+        self.get_first_stmt(fundef2) # no crash, works since 3.7
 
         fundef3 = "def f(i,*,"
         for i in range(253):
@@ -1075,8 +1075,7 @@
             "sole argument"
         many_args = ", ".join("x%i" % i for i in range(256))
         input = "f(%s)" % (many_args,)
-        exc = pytest.raises(SyntaxError, self.get_ast, input).value
-        assert exc.msg == "more than 255 arguments"
+        self.get_ast(input) # doesn't crash any more
         exc = pytest.raises(SyntaxError, self.get_ast, "f((a+b)=c)").value
         assert exc.msg == "keyword can't be an expression"
         exc = pytest.raises(SyntaxError, self.get_ast, "f(a=c, a=d)").value
diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py 
b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -264,6 +264,57 @@
         yield self.st, decl + "x=f(5, b=2, **{'a': 8})", "x", [5, ('a', 8),
                                                                   ('b', 2)]
 
+    def test_funccalls_all_combinations(self):
+        decl = """
+def f(*args, **kwds):
+    kwds = sorted(kwds.items())
+    return list(args) + kwds
+
+class A:
+    def f(self, *args, **kwds):
+        kwds = sorted(kwds.items())
+        return ["meth"] + list(args) + kwds
+a = A()
+"""
+        allres = []
+        allcalls = []
+        for meth in [False, True]:
+            for starstarargs in [
+                    [],
+                    [[('x', 1), ('y', 12)]],
+                    [[('w1', 1), ('w2', 12)], [('x1', 1), ('x2', -12)], 
[('y1', 10), ('y2', 123)]]
+                    ]:
+                for starargs in [[], [(2, 3)], [(2, 3), (4, 19), (23, 54, 
123)]]:
+                    for kwargs in [[], [('m', 1)], [('n', 1), ('o', 2), ('p', 
3)]]:
+                        for args in [(), (1, ), (1, 4, 5)]:
+                            if not meth:
+                                call = "f("
+                                res = []
+                            else:
+                                call = "a.f("
+                                res = ["meth"]
+                            if args:
+                                call += ", ".join(str(arg) for arg in args) + 
","
+                                res.extend(args)
+                            if starargs:
+                                for stararg in starargs:
+                                    call += "*" + str(stararg) + ","
+                                    res.extend(stararg)
+                            if kwargs:
+                                call += ", ".join("%s=%s" % (kw, arg) for (kw, 
arg) in kwargs) + ", "
+                                res.extend(kwargs)
+                            if starstarargs:
+                                for starstar in starstarargs:
+                                    call += "**dict(%s)" % starstar + ","
+                                res.extend(sum(starstarargs, []))
+                            call += ")"
+                            allcalls.append(call)
+                            allres.append(res)
+                            print call
+                            print res
+        self.st(decl + "x=[" + "\n,".join(allcalls) + "]", "x", allres)
+
+
     def test_kwonly(self):
         decl = py.code.Source("""
             def f(a, *, b):
@@ -1276,6 +1327,24 @@
             x = [y for (x, y) in dis.findlinestarts(co)]
         """, 'x', [4]
 
+    def test_many_args(self):
+        args = ["a%i" % i for i in range(300)]
+        argdef = ", ".join(args)
+        res = "+".join(args)
+        callargs = ", ".join(str(i) for i in range(300))
+
+        source1 = """def f(%s):
+            return %s
+x = f(%s)
+        """ % (argdef, res, callargs)
+        source2 = """def f(%s):
+            return %s
+x = f(*(%s))
+        """ % (argdef, res, callargs)
+
+        yield self.simple_test, source1, 'x', sum(range(300))
+        yield self.simple_test, source2, 'x', sum(range(300))
+
 
 class TestCompilerRevDB(BaseTestCompiler):
     spaceconfig = {"translation.reverse_debugger": True}
@@ -1611,6 +1680,39 @@
         counts = self.count_instructions(source)
         assert counts[ops.BUILD_TUPLE] == 1
 
+    def test_call_bytecodes(self):
+        # check that the expected bytecodes are generated
+        source = """def f(): x(a, b, c)"""
+        counts = self.count_instructions(source)
+        assert counts[ops.CALL_FUNCTION] == 1
+
+        source = """def f(): x(a, b, c, x=1, y=2)"""
+        counts = self.count_instructions(source)
+        assert counts[ops.CALL_FUNCTION_KW] == 1
+
+        source = """def f(): x(a, b, c, *(d, 2), x=1, y=2)"""
+        counts = self.count_instructions(source)
+        assert counts[ops.BUILD_TUPLE] == 2
+        assert counts[ops.BUILD_TUPLE_UNPACK] == 1
+        assert counts[ops.CALL_FUNCTION_EX] == 1
+
+        source = """def f(): x(a, b, c, **kwargs)"""
+        counts = self.count_instructions(source)
+        assert counts[ops.BUILD_TUPLE] == 1
+        assert counts[ops.CALL_FUNCTION_EX] == 1
+
+        source = """def f(): x(**kwargs)"""
+        counts = self.count_instructions(source)
+        assert ops.BUILD_TUPLE not in counts # LOAD_CONST used instead
+        assert counts[ops.CALL_FUNCTION_EX] == 1
+
+        source = """def f(): x.m(a, b, c)"""
+        counts = self.count_instructions(source)
+        assert counts[ops.CALL_METHOD] == 1
+
+        source = """def f(): x.m(a, b, c, y=1)"""
+        counts = self.count_instructions(source)
+        assert counts[ops.CALL_METHOD_KW] == 1
 
 class TestHugeStackDepths:
     def run_and_check_stacksize(self, source):
@@ -1653,3 +1755,12 @@
         source = "{" + ",".join(['%s: None' % (i, ) for i in range(200)]) + 
"}\n"
         w_res = self.run_and_check_stacksize(source)
         assert self.space.unwrap(w_res) == dict.fromkeys(range(200))
+
+    def test_callargs(self):
+        source = "(lambda *args: args)(" + ", ".join([str(i) for i in 
range(200)]) + ")\n"
+        w_res = self.run_and_check_stacksize(source)
+        assert self.space.unwrap(w_res) == tuple(range(200))
+
+        source = "(lambda **args: args)(" + ", ".join(["s%s=None" % i for i in 
range(200)]) + ")\n"
+        w_res = self.run_and_check_stacksize(source)
+        assert self.space.unwrap(w_res) == dict.fromkeys(["s" + str(i) for i 
in range(200)])
diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -38,7 +38,7 @@
 # time you make pyc files incompatible.  This value ends up in the frozen
 # importlib, via MAGIC_NUMBER in module/_frozen_importlib/__init__.
 
-pypy_incremental_magic = 208 # bump it by 16
+pypy_incremental_magic = 224 # bump it by 16
 assert pypy_incremental_magic % 16 == 0
 assert pypy_incremental_magic < 3000 # the magic number of Python 3. There are
                                      # no known magic numbers below this value
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -504,6 +504,7 @@
         """ Returns 'funcname()' from either a function name fnname or a
         wrapped callable w_function. If it's not a function or a method, 
returns
         'Classname object'"""
+        # XXX this is super annoying to compute every time we do a function 
call!
         # CPython has a similar function, PyEval_GetFuncName
         from pypy.interpreter.function import Function, Method
         if fnname is not None:
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -273,12 +273,12 @@
                 self.CALL_FUNCTION(oparg, next_instr)
             elif opcode == opcodedesc.CALL_FUNCTION_KW.index:
                 self.CALL_FUNCTION_KW(oparg, next_instr)
-            elif opcode == opcodedesc.CALL_FUNCTION_VAR.index:
-                self.CALL_FUNCTION_VAR(oparg, next_instr)
-            elif opcode == opcodedesc.CALL_FUNCTION_VAR_KW.index:
-                self.CALL_FUNCTION_VAR_KW(oparg, next_instr)
+            elif opcode == opcodedesc.CALL_FUNCTION_EX.index:
+                self.CALL_FUNCTION_EX(oparg, next_instr)
             elif opcode == opcodedesc.CALL_METHOD.index:
                 self.CALL_METHOD(oparg, next_instr)
+            elif opcode == opcodedesc.CALL_METHOD_KW.index:
+                self.CALL_METHOD_KW(oparg, next_instr)
             elif opcode == opcodedesc.COMPARE_OP.index:
                 self.COMPARE_OP(oparg, next_instr)
             elif opcode == opcodedesc.DELETE_ATTR.index:
@@ -1339,31 +1339,54 @@
         self.pushvalue(w_result)
 
     def CALL_FUNCTION(self, oparg, next_instr):
-        # XXX start of hack for performance
-        if (oparg >> 8) & 0xff == 0:
-            # Only positional arguments
-            nargs = oparg & 0xff
-            w_function = self.peekvalue(nargs)
-            try:
-                w_result = self.space.call_valuestack(w_function, nargs, self)
-            finally:
-                self.dropvalues(nargs + 1)
-            self.pushvalue(w_result)
-        # XXX end of hack for performance
+        # Only positional arguments
+        nargs = oparg & 0xff
+        w_function = self.peekvalue(nargs)
+        try:
+            w_result = self.space.call_valuestack(w_function, nargs, self)
+        finally:
+            self.dropvalues(nargs + 1)
+        self.pushvalue(w_result)
+
+    @jit.unroll_safe
+    def CALL_FUNCTION_KW(self, n_arguments, next_instr):
+        w_tup_varnames = self.popvalue()
+        keywords_w = self.space.fixedview(w_tup_varnames)
+        n_keywords = len(keywords_w)
+        n_arguments -= n_keywords
+        keywords = [self.space.text_w(w_keyword) for w_keyword in keywords_w]
+        keywords_w = [None] * n_keywords
+        while True:
+            n_keywords -= 1
+            if n_keywords < 0:
+                break
+            w_value = self.popvalue()
+            keywords_w[n_keywords] = w_value
+        arguments = self.popvalues(n_arguments)
+        w_function  = self.popvalue()
+        args = self.argument_factory(arguments, keywords, keywords_w, None, 
None,
+                                     w_function=w_function)
+        if self.get_is_being_profiled() and 
function.is_builtin_code(w_function):
+            w_result = self.space.call_args_and_c_profile(self, w_function,
+                                                          args)
         else:
-            # general case
-            self.call_function(oparg)
+            w_result = self.space.call_args(w_function, args)
+        self.pushvalue(w_result)
 
-    def CALL_FUNCTION_VAR(self, oparg, next_instr):
-        self.call_function(oparg, has_vararg=True)
-
-    def CALL_FUNCTION_KW(self, oparg, next_instr):
-        w_varkw = self.popvalue()
-        self.call_function(oparg, w_varkw)
-
-    def CALL_FUNCTION_VAR_KW(self, oparg, next_instr):
-        w_varkw = self.popvalue()
-        self.call_function(oparg, w_varkw, has_vararg=True)
+    def CALL_FUNCTION_EX(self, has_kwarg, next_instr):
+        w_kwargs = None
+        if has_kwarg:
+            w_kwargs = self.popvalue()
+        w_args = self.popvalue()
+        w_function = self.popvalue()
+        args = self.argument_factory(
+            [], None, None, w_star=w_args, w_starstar=w_kwargs, 
w_function=w_function)
+        if self.get_is_being_profiled() and 
function.is_builtin_code(w_function):
+            w_result = self.space.call_args_and_c_profile(self, w_function,
+                                                          args)
+        else:
+            w_result = self.space.call_args(w_function, args)
+        self.pushvalue(w_result)
 
     @jit.unroll_safe
     def MAKE_FUNCTION(self, oparg, next_instr):
@@ -1436,6 +1459,7 @@
     # overridden by faster version in the standard object space.
     LOOKUP_METHOD = LOAD_ATTR
     CALL_METHOD = CALL_FUNCTION
+    CALL_METHOD_KW = CALL_FUNCTION_KW
 
     def MISSING_OPCODE(self, oparg, next_instr):
         ofs = self.last_instr
@@ -1489,30 +1513,29 @@
         self.pushvalue(w_set)
 
     @jit.unroll_safe
-    def list_unpack_helper(frame, itemcount):
-        space = frame.space
-        w_sum = space.newlist([], sizehint=itemcount)
-        for i in range(itemcount, 0, -1):
-            w_item = frame.peekvalue(i-1)
-            w_sum.extend(w_item)
-        frame.popvalues(itemcount)
-        return w_sum
+    def BUILD_TUPLE_UNPACK(self, itemcount, next_instr):
+        l = []
+        for i in range(itemcount-1, -1, -1):
+            w_item = self.peekvalue(i)
+            l.extend(self.space.fixedview(w_item))
+        self.popvalues(itemcount)
+        self.pushvalue(self.space.newtuple(l[:]))
 
     @jit.unroll_safe
-    def BUILD_TUPLE_UNPACK(self, itemcount, next_instr):
-        w_list = self.list_unpack_helper(itemcount)
-        items = [w_obj for w_obj in w_list.getitems_unroll()]
-        self.pushvalue(self.space.newtuple(items))
-
     def BUILD_LIST_UNPACK(self, itemcount, next_instr):
-        w_sum = self.list_unpack_helper(itemcount)
+        space = self.space
+        w_sum = space.newlist([], sizehint=itemcount)
+        for i in range(itemcount-1, -1, -1):
+            w_item = self.peekvalue(i)
+            w_sum.extend(w_item)
+        self.popvalues(itemcount)
         self.pushvalue(w_sum)
 
     def BUILD_MAP_UNPACK(self, itemcount, next_instr):
         self._build_map_unpack(itemcount, with_call=False)
 
     def BUILD_MAP_UNPACK_WITH_CALL(self, oparg, next_instr):
-        num_maps = oparg & 0xff
+        num_maps = oparg # XXX CPython generates better error messages
         self._build_map_unpack(num_maps, with_call=True)
 
     @jit.unroll_safe
diff --git a/pypy/objspace/std/callmethod.py b/pypy/objspace/std/callmethod.py
--- a/pypy/objspace/std/callmethod.py
+++ b/pypy/objspace/std/callmethod.py
@@ -85,42 +85,47 @@
 @jit.unroll_safe
 def CALL_METHOD(f, oparg, *ignored):
     # opargs contains the arg, and kwarg count, excluding the implicit 'self'
-    n_args = oparg & 0xff
-    n_kwargs = (oparg >> 8) & 0xff
-    w_self = f.peekvalue_maybe_none(n_args + (2 * n_kwargs))
+    n_args = oparg
+    w_self = f.peekvalue_maybe_none(n_args)
     n = n_args + (w_self is not None)
 
-    if not n_kwargs:
-        w_callable = f.peekvalue(n_args + (2 * n_kwargs) + 1)
-        try:
-            w_result = f.space.call_valuestack(
-                    w_callable, n, f, methodcall=w_self is not None)
-        finally:
-            f.dropvalues(n_args + 2)
+    w_callable = f.peekvalue(n_args + 1)
+    try:
+        w_result = f.space.call_valuestack(
+                w_callable, n, f, methodcall=w_self is not None)
+    finally:
+        f.dropvalues(n_args + 2)
+    f.pushvalue(w_result)
+
[email protected]_safe
+def CALL_METHOD_KW(f, n_arguments, *ignored):
+    # opargs contains the arg + kwarg count, excluding the implicit 'self'
+    w_self = f.peekvalue_maybe_none(n_arguments + 1)
+    w_tup_varnames = f.popvalue()
+    keywords_w = f.space.fixedview(w_tup_varnames)
+    n_keywords = len(keywords_w)
+    n_arguments -= n_keywords
+    n = n_arguments + (w_self is not None)
+    keywords = [f.space.text_w(w_keyword) for w_keyword in keywords_w]
+    keywords_w = [None] * n_keywords
+    while True:
+        n_keywords -= 1
+        if n_keywords < 0:
+            break
+        w_value = f.popvalue()
+        keywords_w[n_keywords] = w_value
+
+    arguments = f.popvalues(n)    # includes w_self if it is not None
+    if w_self is None:
+        f.popvalue_maybe_none()    # removes w_self, which is None
+    w_callable = f.popvalue()
+    args = f.argument_factory(
+            arguments, keywords, keywords_w, None, None,
+            methodcall=w_self is not None, w_function=w_callable)
+    if f.get_is_being_profiled() and function.is_builtin_code(w_callable):
+        w_result = f.space.call_args_and_c_profile(f, w_callable, args)
     else:
-        keywords = [None] * n_kwargs
-        keywords_w = [None] * n_kwargs
-        while True:
-            n_kwargs -= 1
-            if n_kwargs < 0:
-                break
-            w_value = f.popvalue()
-            w_key = f.popvalue()
-            key = f.space.text_w(w_key)
-            keywords[n_kwargs] = key
-            keywords_w[n_kwargs] = w_value
-
-        arguments = f.popvalues(n)    # includes w_self if it is not None
-        if w_self is None:
-            f.popvalue_maybe_none()    # removes w_self, which is None
-        w_callable = f.popvalue()
-        args = f.argument_factory(
-                arguments, keywords, keywords_w, None, None,
-                methodcall=w_self is not None, w_function=w_callable)
-        if f.get_is_being_profiled() and function.is_builtin_code(w_callable):
-            w_result = f.space.call_args_and_c_profile(f, w_callable, args)
-        else:
-            w_result = f.space.call_args(w_callable, args)
+        w_result = f.space.call_args(w_callable, args)
     f.pushvalue(w_result)
 
 
diff --git a/pypy/objspace/std/frame.py b/pypy/objspace/std/frame.py
--- a/pypy/objspace/std/frame.py
+++ b/pypy/objspace/std/frame.py
@@ -85,7 +85,8 @@
         StdObjSpaceFrame.INPLACE_SUBTRACT = int_INPLACE_SUBTRACT
     if space.config.objspace.std.optimized_list_getitem:
         StdObjSpaceFrame.BINARY_SUBSCR = list_BINARY_SUBSCR
-    from pypy.objspace.std.callmethod import LOOKUP_METHOD, CALL_METHOD
+    from pypy.objspace.std.callmethod import LOOKUP_METHOD, CALL_METHOD, 
CALL_METHOD_KW
     StdObjSpaceFrame.LOOKUP_METHOD = LOOKUP_METHOD
     StdObjSpaceFrame.CALL_METHOD = CALL_METHOD
+    StdObjSpaceFrame.CALL_METHOD_KW = CALL_METHOD_KW
     return StdObjSpaceFrame
diff --git a/pypy/tool/opcode3.py b/pypy/tool/opcode3.py
--- a/pypy/tool/opcode3.py
+++ b/pypy/tool/opcode3.py
@@ -39,6 +39,7 @@
 del op
 
 def def_op(name, op):
+    assert op not in opname
     opname[op] = name
     opmap[name] = op
 
@@ -173,11 +174,10 @@
 haslocal.append(126)
 
 def_op('RAISE_VARARGS', 130)    # Number of raise arguments (1, 2, or 3)
-def_op('CALL_FUNCTION', 131)    # #args + (#kwargs << 8)
+def_op('CALL_FUNCTION', 131)    # #args
 hasnargs.append(131)
 def_op('MAKE_FUNCTION', 132)    # Number of args with default values
 def_op('BUILD_SLICE', 133)      # Number of items
-def_op('MAKE_CLOSURE', 134)
 def_op('LOAD_CLOSURE', 135)
 hasfree.append(135)
 def_op('LOAD_DEREF', 136)
@@ -187,12 +187,9 @@
 def_op('DELETE_DEREF', 138)
 hasfree.append(138)
 
-def_op('CALL_FUNCTION_VAR', 140)     # #args + (#kwargs << 8)
-hasnargs.append(140)
-def_op('CALL_FUNCTION_KW', 141)      # #args + (#kwargs << 8)
+def_op('CALL_FUNCTION_KW', 141)  # #args + #kwargs
 hasnargs.append(141)
-def_op('CALL_FUNCTION_VAR_KW', 142)  # #args + (#kwargs << 8)
-hasnargs.append(142)
+def_op('CALL_FUNCTION_EX', 142)  # Flags
 
 jrel_op('SETUP_WITH', 143)
 
@@ -223,6 +220,7 @@
 hasname.append(201)
 def_op('CALL_METHOD', 202)            # #args not including 'self'
 def_op('BUILD_LIST_FROM_ARG', 203)
+def_op('CALL_METHOD_KW', 204)
 
 name_op('LOAD_METHOD', 160)
 def_op('CALL_METHOD', 161)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to