Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize the "all" built-in #407

Merged
merged 13 commits into from Jul 27, 2019
82 changes: 82 additions & 0 deletions Developer_Manual.rst
Expand Up @@ -2578,6 +2578,88 @@ given, which would be ``sys.stdout`` in a rather hard-coded way (no variable
look-ups involved).


Builtin ``zip`` for Python2
---------------------------

.. code-block:: python

def _zip(a, b, c, ... ):
# First assign, to preserve order of execution,
# the arguments might be complex expressions.
tmp_arg1 = a
tmp_arg2 = b
tmp_arg3 = c
...

tmp_iter_1 = iter(tmp_arg1)
tmp_iter_2 = iter(tmp_arg2)
tmp_iter_3 = iter(tmp_arg3)
...

# could be more
tmp_result = []
try:
while 1:
tmp_result.append(
(
next(tmp_iter_1),
next(tmp_iter_2),
next(tmp_iter_3),
...
)
)
except StopIteration:
pass

return tmp_result

Builtin ``map`` for Python2
---------------------------

.. code-block:: python

def _map():
...

Builtin ``min``
---------------

.. code-block:: python

# TODO: keyfunc (Python2/3), defaults (Python3)
def _min(a, b, c, ...):
tmp_arg1 = a
tmp_arg2 = b
tmp_arg3 = c
...

result = tmp_arg1
if keyfunc is None: # can be decided during re-formulation
tmp_key_result = keyfunc(result)
tmp_key_candidate = keyfunc(tmp_arg2)
if tmp_key_candidate < tmp_key_result:
result = tmp_arg2
tmp_key_result = tmp_key_candidate
tmp_key_candidate = keyfunc(tmp_arg3)
if tmp_key_candidate < tmp_key_result:
result = tmp_arg3
tmp_key_result = tmp_key_candidate
...
else:
if tmp_arg2 < result:
result = tmp_arg2
if tmp_arg3 < result:
result = tmp_arg3
...

return result


Builtin ``max``
---------------

See ``min`` just with ``>`` instead of ``<``.

Call to ``dir`` without arguments
---------------------------------

Expand Down
3 changes: 3 additions & 0 deletions nuitka/build/include/nuitka/helpers.h
Expand Up @@ -253,6 +253,9 @@ extern PyObject *BUILTIN_ANY(PyObject *value);
// For built-in built-in super() functionality.
extern PyObject *BUILTIN_SUPER(PyObject *type, PyObject *object);

// For built-in built-in all() functionality.
extern PyObject *BUILTIN_ALL(PyObject *value);

// The patched isinstance() functionality used for the built-in.
extern int Nuitka_IsInstance(PyObject *inst, PyObject *cls);

Expand Down
42 changes: 42 additions & 0 deletions nuitka/build/static_src/CompiledCodeHelpers.c
Expand Up @@ -541,6 +541,48 @@ PyObject *BUILTIN_XRANGE3(PyObject *low, PyObject *high, PyObject *step) {
#endif
}

PyObject *BUILTIN_ALL(PyObject *value) {
CHECK_OBJECT(value);

PyObject *it = PyObject_GetIter(value);

if (unlikely((it == NULL)))
{
return NULL;
}

iternextfunc iternext = Py_TYPE(it)->tp_iternext;
for (;;)
{
PyObject *item = iternext(it);

if (unlikely((item == NULL)))
break;
int cmp = PyObject_IsTrue(item);
Py_DECREF(item);
if (unlikely(cmp < 0))
{
Py_DECREF(it);
return NULL;
}
if (cmp == 0)
{
Py_DECREF(it);
Py_INCREF(Py_False);
return Py_False;
}
}

Py_DECREF(it);
if (unlikely(!CHECK_AND_CLEAR_STOP_ITERATION_OCCURRED()))
{
return NULL;
}

Py_INCREF(Py_True);
return Py_True;
}

PyObject *BUILTIN_LEN(PyObject *value) {
CHECK_OBJECT(value);

Expand Down
2 changes: 2 additions & 0 deletions nuitka/codegen/CodeGeneration.py
Expand Up @@ -158,6 +158,7 @@
)
from .IteratorCodes import (
generateBuiltinAnyCode,
generateBuiltinAllCode,
generateBuiltinIter1Code,
generateBuiltinIter2Code,
generateBuiltinIterForUnpackCode,
Expand Down Expand Up @@ -526,6 +527,7 @@ def makeGlobalContext():
"EXPRESSION_BUILTIN_SET": generateBuiltinSetCode,
"EXPRESSION_BUILTIN_ANY": generateBuiltinAnyCode,
"EXPRESSION_BUILTIN_FROZENSET": generateBuiltinFrozensetCode,
"EXPRESSION_BUILTIN_ALL": generateBuiltinAllCode,
"EXPRESSION_BUILTIN_DICT": generateBuiltinDictCode,
"EXPRESSION_BUILTIN_LOCALS_COPY": generateBuiltinLocalsCode,
"EXPRESSION_BUILTIN_LOCALS_UPDATED": generateBuiltinLocalsCode,
Expand Down
13 changes: 13 additions & 0 deletions nuitka/codegen/IteratorCodes.py
Expand Up @@ -273,3 +273,16 @@ def generateBuiltinAnyCode(to_name, expression, emit, context):
emit=emit,
context=context,
)


def generateBuiltinAllCode(to_name, expression, emit, context):
generateCAPIObjectCode(
to_name=to_name,
capi="BUILTIN_ALL",
arg_desc=(("all_arg", expression.getValue()),),
may_raise=expression.mayRaiseException(BaseException),
conversion_check=decideConversionCheckNeeded(to_name, expression),
source_ref=expression.getCompatibleSourceReference(),
emit=emit,
context=context,
)
117 changes: 117 additions & 0 deletions nuitka/nodes/BuiltinAllNodes.py
@@ -0,0 +1,117 @@
# Copyright 2019, Kay Hayen, mailto:kay.hayen@gmail.com
# Batakrishna Sahu, mailto:bablusahoo16@gmail.com
#
# Part of "Nuitka", an optimizing Python compiler that is compatible and
# integrates with CPython, but also works on its own.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" Node for the calls to the 'all' built-in.

"""

from nuitka.specs import BuiltinParameterSpecs

from .ExpressionBases import ExpressionBuiltinSingleArgBase
from .NodeMakingHelpers import (
makeConstantReplacementNode,
makeRaiseTypeErrorExceptionReplacementFromTemplateAndValue,
wrapExpressionWithNodeSideEffects,
)
from .shapes.BuiltinTypeShapes import (
ShapeTypeBool,
ShapeTypeBytes,
ShapeTypeStr,
ShapeTypeUnicode,
)


class ExpressionBuiltinAll(ExpressionBuiltinSingleArgBase):
""" Builtin All Node class.

Args:
ExpressionBase: 'all - expression'

Returns:
Node that represents built-in 'all' call.

"""

kind = "EXPRESSION_BUILTIN_ALL"

builtin_spec = BuiltinParameterSpecs.builtin_all_spec

def computeExpression(self, trace_collection):
value = self.getValue()
shape = value.getTypeShape()
if shape.hasShapeSlotIter() is False:
return makeRaiseTypeErrorExceptionReplacementFromTemplateAndValue(
template="'%s' object is not iterable",
operation="all",
original_node=value,
value_node=value,
)

if shape in (ShapeTypeStr, ShapeTypeBytes, ShapeTypeUnicode):
return (
wrapExpressionWithNodeSideEffects(
new_node=makeConstantReplacementNode(constant=True, node=self),
old_node=value,
),
"new_constant",
"Predicted truth value of built-in 'all' string type argument",
)

iteration_handle = value.getIterationHandle()

if iteration_handle is not None:
all_true = iteration_handle.getAllElementTruthValue()

if all_true is not None:
result = wrapExpressionWithNodeSideEffects(
new_node=makeConstantReplacementNode(constant=all_true, node=self),
old_node=value,
)

return (
result,
"new_constant",
"Predicted truth value of built-in 'all' argument",
)

self.onContentEscapes(trace_collection)

# All code could be run, note that.
trace_collection.onControlFlowEscape(self)

# All exception may be raised.
trace_collection.onExceptionRaiseExit(BaseException)

return self, None, None

def getTypeShape(self):
""" returns type shape of the 'all' node

"""
return ShapeTypeBool

def mayRaiseException(self, exception_type):
""" returns boolean True if try/except/finally is needed else False

"""
value = self.getValue()

if value.mayRaiseException(exception_type):
return True

return not value.getTypeShape().hasShapeSlotIter()
35 changes: 35 additions & 0 deletions nuitka/nodes/IterationHandles.py
Expand Up @@ -49,6 +49,33 @@ def getNextValueTruth(self):
return StopIteration
return iteration_value.getTruthValue()

def getAllElementTruthValue(self):
"""Returns truth value for 'all' on 'lists'. It returns
True: if all the elements of the list are True,
False: if any element in the list is False,
None: if number of elements in the list is greater than
256 or any element is Unknown.
"""
all_true = True
count = 0
while True:
truth_value = self.getNextValueTruth()
if truth_value is StopIteration:
break

if count > 256:
return None

if truth_value is False:
return False

if truth_value is None:
all_true = None

count += 1

return all_true


class ConstantIterationHandleBase(IterationHandleBase):
"""Base class for the Constant Iteration Handles.
Expand Down Expand Up @@ -263,6 +290,10 @@ def getNextValueTruth(self):
return StopIteration
return bool(iteration_value)

@staticmethod
def getAllElementTruthValue():
return True


class ConstantIterationHandleRange1(ConstantRangeIterationHandleBase):
"""Iteration handle for range(low,)"""
Expand All @@ -275,6 +306,10 @@ def __init__(self, low_value, source_ref):
def getIterationLength(self):
return max(0, self.low)

@staticmethod
def getAllElementTruthValue():
return False


class ConstantIterationHandleRange2(ConstantRangeIterationHandleBase):
"""Iteration handle for ranges(low, high)"""
Expand Down