Commit 0ed4800a authored by Dag Sverre Seljebotn's avatar Dag Sverre Seljebotn

Prototype for None-checking. *Very* unstable.

parent fb59a89d
...@@ -830,10 +830,12 @@ class NameNode(AtomicExprNode): ...@@ -830,10 +830,12 @@ class NameNode(AtomicExprNode):
# #
# entry Entry Symbol table entry # entry Entry Symbol table entry
# interned_cname string # interned_cname string
# possible_var_values object See Optimize.FindPossibleVariableValues
is_name = 1 is_name = 1
skip_assignment_decref = False skip_assignment_decref = False
entry = None entry = None
possible_var_values = None
def create_analysed_rvalue(pos, env, entry): def create_analysed_rvalue(pos, env, entry):
node = NameNode(pos) node = NameNode(pos)
...@@ -2073,6 +2075,7 @@ class AttributeNode(ExprNode): ...@@ -2073,6 +2075,7 @@ class AttributeNode(ExprNode):
# #
# obj ExprNode # obj ExprNode
# attribute string # attribute string
# needs_none_check boolean Used if obj is an extension type.
# #
# Used internally: # Used internally:
# #
...@@ -2089,6 +2092,7 @@ class AttributeNode(ExprNode): ...@@ -2089,6 +2092,7 @@ class AttributeNode(ExprNode):
result = "<error>" result = "<error>"
entry = None entry = None
is_called = 0 is_called = 0
needs_none_check = True
def coerce_to(self, dst_type, env): def coerce_to(self, dst_type, env):
# If coercing to a generic pyobject and this is a cpdef function # If coercing to a generic pyobject and this is a cpdef function
...@@ -2317,6 +2321,15 @@ class AttributeNode(ExprNode): ...@@ -2317,6 +2321,15 @@ class AttributeNode(ExprNode):
self.obj.py_result(), self.obj.py_result(),
self.interned_attr_cname, self.interned_attr_cname,
code.error_goto_if_null(self.result_code, self.pos))) code.error_goto_if_null(self.result_code, self.pos)))
else:
# result_code contains what is needed, but we may need to insert
# a check and raise an exception
if self.obj.type.is_extension_type and self.needs_none_check:
code.globalstate.use_utility_code(raise_noneattr_error_utility_code)
code.putln("if (%s) {" % code.unlikely("%s == Py_None") % self.obj.result_as(PyrexTypes.py_object_type))
code.putln("__Pyx_RaiseNoneAttributeError(\"%s\");" % self.attribute.encode("UTF-8")) # todo: fix encoding
code.putln(code.error_goto(self.pos))
code.putln("}")
def generate_assignment_code(self, rhs, code): def generate_assignment_code(self, rhs, code):
self.obj.generate_evaluation_code(code) self.obj.generate_evaluation_code(code)
...@@ -4551,3 +4564,13 @@ static INLINE int __Pyx_SetItemInt(PyObject *o, Py_ssize_t i, PyObject *v, int i ...@@ -4551,3 +4564,13 @@ static INLINE int __Pyx_SetItemInt(PyObject *o, Py_ssize_t i, PyObject *v, int i
""", """,
""" """
"""] """]
#------------------------------------------------------------------------------------
raise_noneattr_error_utility_code = [
"""
static INLINE void __Pyx_RaiseNoneAttributeError(char* attrname);
""", """
static INLINE void __Pyx_RaiseNoneAttributeError(char* attrname) {
PyErr_Format(PyExc_AttributeError, "'NoneType' object has no attribute '%s'", attrname);
}
"""]
...@@ -81,6 +81,7 @@ class Context: ...@@ -81,6 +81,7 @@ class Context:
from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from ParseTreeTransforms import ResolveOptions from ParseTreeTransforms import ResolveOptions
from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting
from Optimize import OptimizeNoneChecking, FindPossibleVariableValues
from Buffer import IntroduceBufferAuxiliaryVars from Buffer import IntroduceBufferAuxiliaryVars
from ModuleNode import check_c_classes from ModuleNode import check_c_classes
...@@ -104,7 +105,9 @@ class Context: ...@@ -104,7 +105,9 @@ class Context:
_check_c_classes, _check_c_classes,
AnalyseExpressionsTransform(self), AnalyseExpressionsTransform(self),
SwitchTransform(), SwitchTransform(),
FindPossibleVariableValues(self),
OptimizeRefcounting(self), OptimizeRefcounting(self),
OptimizeNoneChecking(self),
# SpecialFunctions(self), # SpecialFunctions(self),
# CreateClosureClasses(context), # CreateClosureClasses(context),
] ]
......
...@@ -149,3 +149,119 @@ class OptimizeRefcounting(Visitor.CythonTransform): ...@@ -149,3 +149,119 @@ class OptimizeRefcounting(Visitor.CythonTransform):
# Set a flag in NameNode to skip the decref # Set a flag in NameNode to skip the decref
lhs.skip_assignment_decref = True lhs.skip_assignment_decref = True
return node return node
class ExtTypePossibleValues:
can_be_none = True
def copy_with(self, can_be_none=None):
result = ExtTypePossibleValues()
if can_be_none is not None:
result.can_be_none = can_be_none
return result
def new(self):
"Polymorphic constructor"
return ExtTypePossibleValues()
class FindPossibleVariableValues(Visitor.CythonTransform):
"""
Annotates NameNodes with information about the possible values
the variable referred to can take, *at that point* in the execution.
This is done on a best effort basis, so we can be as smart or dumb
as we want. A do-nothing-op should always be valid.
Each type of variable keeps a different type of "variable range"
information.
This information is invalid if the tree is reorganized (read:
keep this transform late in the pipeline).
Currently this is done:
- Extension types gets flagged
"""
#
# Manage info stack
#
def create_empty_knowledge(self, scope):
knowledge = {}
for entry in scope.entries.values():
if entry.type.is_extension_type:
knowledge[entry] = ExtTypePossibleValues()
return knowledge
def visit_ModuleNode(self, node):
self.knowledge = self.create_empty_knowledge(node.scope)
self.visitchildren(node)
return node
def visit_FuncDefNode(self, node):
oldknow = self.knowledge
self.knowledge = self.create_empty_knowledge(node.local_scope)
self.visitchildren(node)
self.knowledge = oldknow
return node
def visit_NameNode(self, node):
node.possible_var_values = self.knowledge.get(node.entry, None)
return node
#
# Conditions which restrict possible variable values
#
def visit_IfClauseNode(self, clause):
def process():
self.visitchildren(clause)
return clause
# we're lazy and only check in one specific easy case: single comparison with None
# the code is a bit nasty but handling the proper cases will force through better code
# anyway
cond = clause.condition
if not isinstance(cond, ExprNodes.PrimaryCmpNode): return process()
if clause.condition.cascade is not None: return process()
if isinstance(cond.operand1, ExprNodes.NoneNode):
operand_checked = cond.operand2
elif isinstance(cond.operand2, ExprNodes.NoneNode):
operand_checked = cond.operand1
else:
return process()
if not isinstance(operand_checked, ExprNodes.NameNode):
return process()
entry = operand_checked.entry
if entry not in self.knowledge:
# Not tracking this variable
return process()
# Finally!
if cond.operator == 'is_not':
# Within this block we can assume the variable is not None
# (until it is reassigned)
self.visitchildren(clause, attrs=["condition"])
oldvalues = self.knowledge[entry]
self.knowledge[entry] = oldvalues.copy_with(can_be_none=False)
self.visitchildren(clause, attrs=["body"])
self.knowledge[entry] = oldvalues
return clause
else:
return process()
# Assignments which reset possible variable values
def visit_SingleAssignmentNode(self, node):
if isinstance(node.lhs, ExprNodes.NameNode):
entry = node.lhs.entry
if entry in self.knowledge:
self.knowledge[entry] = self.knowledge[entry].new()
self.visitchildren(node)
return node
class OptimizeNoneChecking(Visitor.CythonTransform):
def visit_AttributeNode(self, node):
if isinstance(node.obj, ExprNodes.NameNode):
obj = node.obj
if obj.type.is_extension_type and not obj.possible_var_values.can_be_none:
node.needs_none_check = False
self.visitchildren(node)
return node
"""
Tests accessing attributes of extension type variables
set to None
>>> obj = MyClass(2, 3)
>>> func(obj)
2
>>> func(None)
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'a'
>>> checking(obj)
2
2
>>> checking(None)
var is None
>>> check_and_assign(obj)
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'a'
"""
cdef class MyClass:
cdef int a, b
def __init__(self, a, b):
self.a = a
self.b = b
def func(MyClass var):
print var.a
def some():
return MyClass(4, 5)
def checking(MyClass var):
state = (var is None)
if not state:
print var.a
if var is not None:
print var.a
else:
print "var is None"
def check_and_assign(MyClass var):
if var is not None:
print var.a
var = None
print var.a
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment