Commit 12757779 authored by gsamain's avatar gsamain

Allow manual cypclass locking and basic lock bookkeeping

parent 3a2e0601
......@@ -691,6 +691,20 @@ class ExprNode(Node):
def addr_not_const(self):
error(self.pos, "Address is not constant")
def is_rhs_locked(self):
return True
def is_lhs_locked(self):
return True
def check_rhs_locked(self):
if not self.is_rhs_locked():
error(self.pos, "This rhs is not correctly locked (write lock for non-const methods, read lock is sufficient for everything else)")
def check_lhs_locked(self):
if not self.is_lhs_locked():
error(self.pos, "This lhs is not correctly locked (write lock needed)")
# ----------------- Result Allocation -----------------
def result_in_temp(self):
......@@ -5889,6 +5903,9 @@ class SimpleCallNode(CallNode):
self.overflowcheck = env.directives['overflowcheck']
def is_rhs_locked(self):
return self.function.is_rhs_locked()
def calculate_result_code(self):
return self.c_call_code()
......@@ -7177,6 +7194,22 @@ class AttributeNode(ExprNode):
gil_message = "Accessing Python attribute"
def is_rhs_locked(self):
# TODO: some chaining
obj = self.obj
if hasattr(obj, 'entry') and obj.entry.type.is_cyp_class and (obj.entry.is_variable or obj.entry.is_cfunction)\
and not (obj.entry.is_rlocked and (not self.entry.is_cfunction or self.entry.type.is_const_method) or obj.entry.is_wlocked):
return False
return True
def is_lhs_locked(self):
# TODO: some chaining
obj = self.obj
if self.is_lvalue() and hasattr(obj, 'entry') and obj.entry.type.is_cyp_class and not obj.entry.is_wlocked:
return False
return True
def is_cimported_module_without_shadow(self, env):
return self.obj.is_cimported_module_without_shadow(env)
......
......@@ -5373,6 +5373,7 @@ class ExprStatNode(StatNode):
def analyse_expressions(self, env):
self.expr.result_is_used = False # hint that .result() may safely be left empty
self.expr = self.expr.analyse_expressions(env)
self.expr.check_rhs_locked()
# Repeat in case of node replacement.
self.expr.result_is_used = False # hint that .result() may safely be left empty
return self
......@@ -5540,6 +5541,8 @@ class SingleAssignmentNode(AssignmentNode):
self.lhs = self.lhs.analyse_target_types(env)
self.lhs.gil_assignment_check(env)
self.rhs.check_rhs_locked()
self.lhs.check_lhs_locked()
unrolled_assignment = self.unroll_lhs(env)
if unrolled_assignment:
return unrolled_assignment
......@@ -8150,6 +8153,84 @@ class EnsureGILNode(GILExitNode):
def generate_execution_code(self, code):
code.put_ensure_gil(declare_gilstate=False)
class LockCypclassNode(StatNode):
# 'with locked [cypclass object]' or 'with unlocked [cypclass object]' statement
#
# state string 'locked' or 'unlocked'
# obj ExprNode the (un)locked object
# body StatNode
child_attrs = ["body", "obj"]
def analyse_declarations(self, env):
self.body.analyse_declarations(env)
self.obj.analyse_declarations(env)
def analyse_expressions(self, env):
self.obj = self.obj.analyse_types(env)
if not hasattr(self.obj, 'entry'):
error(self.pos, "The (un)locking target has no entry")
if not self.obj.type.is_cyp_class:
error(self.pos, "Cannot (un)lock a non-cypclass variable !")
is_rlocked = self.obj.entry.is_rlocked
is_wlocked = self.obj.entry.is_wlocked
if self.state == "unclocked" and not (is_rlocked or is_wlocked):
error(self.pos, "Cannot unlock an already unlocked object !")
elif self.state == "rlocked" and is_rlocked:
error(self.pos, "Double read lock !")
elif self.state == "wlocked" and is_wlocked:
error(self.pos, "Double write lock !")
# We need to save states because in case of 'with unlocked' statement,
# we must know which lock has to be restored after the with body.
self.was_rlocked = is_rlocked
self.was_wlocked = is_wlocked
if self.state == "rlocked":
self.obj.entry.is_rlocked = True
self.obj.entry.is_wlocked = False
elif self.state == "wlocked":
self.obj.entry.is_rlocked = False
self.obj.entry.is_wlocked = True
else:
self.obj.entry.is_rlocked = False
self.obj.entry.is_wlocked = False
self.body = self.body.analyse_expressions(env)
self.obj.entry.is_rlocked = self.was_rlocked
self.obj.entry.is_wlocked = self.was_wlocked
return self
def generate_execution_code(self, code):
# We must unlock if it's a 'with unlocked' statement,
# or if we're changing lock type.
if self.was_rlocked or self.was_wlocked:
code.putln("Cy_UNLOCK(%s);" % self.obj.result())
# Then, lock accordingly
if self.state == "rlocked":
code.putln("Cy_RLOCK(%s);" % self.obj.result())
elif self.state == "wlocked":
code.putln("Cy_WLOCK(%s);" % self.obj.result())
self.body.generate_execution_code(code)
# We must unlock if we held a lock previously
if self.state != "unlocked":
code.putln("Cy_UNLOCK(%s);" % self.obj.result())
# Then, relock if needed
if self.was_rlocked:
code.putln("Cy_RLOCK(%s);" % self.obj.result())
elif self.was_wlocked:
code.putln("Cy_WLOCK(%s);" % self.obj.result())
def cython_view_utility_code():
from . import MemoryView
......
......@@ -2066,6 +2066,20 @@ def p_with_items(s, is_async=False):
else:
body = p_suite(s)
return Nodes.GILStatNode(pos, state=state, body=body, condition=condition)
elif not s.in_python_file and s.sy == 'IDENT' and s.systring in ('unlocked', 'rlocked', 'wlocked'):
state = s.systring
s.next()
if s.sy != 'IDENT':
s.error("The with %s statement must be followed by the cypclass object to operate on" % state, pos)
obj = p_starred_expr(s)
if s.sy == ',':
s.next()
body = p_with_items(s, is_async=is_async)
else:
body = p_suite(s)
return Nodes.LockCypclassNode(pos, state=state, obj=obj, body=body)
else:
manager = p_test(s)
target = None
......
......@@ -135,6 +135,8 @@ class Entry(object):
# is_fused_specialized boolean Whether this entry of a cdef or def function
# is a specialization
# is_cgetter boolean Is a c-level getter function
# is_wlocked boolean Is locked with a write lock (used for cypclass)
# is_rlocked boolean Is locked with a read lock (used for cypclass)
# TODO: utility_code and utility_code_definition serves the same purpose...
......@@ -205,6 +207,8 @@ class Entry(object):
cf_used = True
outer_entry = None
is_cgetter = False
is_wlocked = False
is_rlocked = False
def __init__(self, name, cname, type, pos = None, init = None):
self.name = name
......
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