Commit 11915abb authored by Xavier Thompson's avatar Xavier Thompson

Introduce 'iso' qualifier and 'consume' keyword

parent b24fad38
...@@ -1048,11 +1048,15 @@ class ExprNode(Node): ...@@ -1048,11 +1048,15 @@ class ExprNode(Node):
src = CoerceToComplexNode(src, dst_type, env) src = CoerceToComplexNode(src, dst_type, env)
else: else:
# neither src nor dst are py types # neither src nor dst are py types
# Qualified cypclass types can have nontrivial aliasing rules
# meaning type equality may not imply the coercion is valid.
if src_type.is_qualified_cyp_class and not dst_type.assignable_from(src_type):
self.fail_assignment(dst_type)
# Added the string comparison, since for c types that # Added the string comparison, since for c types that
# is enough, but Cython gets confused when the types are # is enough, but Cython gets confused when the types are
# in different pxi files. # in different pxi files.
# TODO: Remove this hack and require shared declarations. # TODO: Remove this hack and require shared declarations.
if not (src.type == dst_type or str(src.type) == str(dst_type) or dst_type.assignable_from(src_type)): elif not (src.type == dst_type or str(src.type) == str(dst_type) or dst_type.assignable_from(src_type)):
self.fail_assignment(dst_type) self.fail_assignment(dst_type)
return src return src
...@@ -3945,8 +3949,8 @@ class IndexNode(_IndexingBaseNode): ...@@ -3945,8 +3949,8 @@ class IndexNode(_IndexingBaseNode):
elif self.exception_check == '~': elif self.exception_check == '~':
self.is_temp = True self.is_temp = True
self.index = self.index.coerce_to(func_type.args[0].type, env) self.index = self.index.coerce_to(func_type.args[0].type, env)
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
if setting and not func_type.return_type.is_reference: if setting and not self.type.is_reference:
error(self.pos, "Can't set non-reference result '%s'" % self.type) error(self.pos, "Can't set non-reference result '%s'" % self.type)
return self return self
...@@ -3979,7 +3983,7 @@ class IndexNode(_IndexingBaseNode): ...@@ -3979,7 +3983,7 @@ class IndexNode(_IndexingBaseNode):
self.is_temp = True self.is_temp = True
self.index = self.index.coerce_to(function.type.args[0].type, env) self.index = self.index.coerce_to(function.type.args[0].type, env)
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
return self return self
def analyse_cyp_class_setitem(self, env): def analyse_cyp_class_setitem(self, env):
...@@ -4019,7 +4023,7 @@ class IndexNode(_IndexingBaseNode): ...@@ -4019,7 +4023,7 @@ class IndexNode(_IndexingBaseNode):
env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp")) env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
self.index = self.index.coerce_to(function.type.args[0].type, env) self.index = self.index.coerce_to(function.type.args[0].type, env)
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
return self return self
def analyse_as_c_function(self, env): def analyse_as_c_function(self, env):
...@@ -6234,7 +6238,7 @@ class SimpleCallNode(CallNode): ...@@ -6234,7 +6238,7 @@ class SimpleCallNode(CallNode):
else: else:
self.type = PyrexTypes.CPtrType(self.function.class_type) self.type = PyrexTypes.CPtrType(self.function.class_type)
else: else:
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
if self.function.is_name or self.function.is_attribute: if self.function.is_name or self.function.is_attribute:
func_entry = self.function.entry func_entry = self.function.entry
...@@ -6723,7 +6727,7 @@ class PythonCapiCallNode(SimpleCallNode): ...@@ -6723,7 +6727,7 @@ class PythonCapiCallNode(SimpleCallNode):
def __init__(self, pos, function_name, func_type, def __init__(self, pos, function_name, func_type,
utility_code = None, py_name=None, **kwargs): utility_code = None, py_name=None, **kwargs):
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
self.result_ctype = self.type self.result_ctype = self.type
self.function = PythonCapiFunctionNode( self.function = PythonCapiFunctionNode(
pos, py_name, function_name, func_type, pos, py_name, function_name, func_type,
...@@ -11346,6 +11350,78 @@ class SizeofVarNode(SizeofNode): ...@@ -11346,6 +11350,78 @@ class SizeofVarNode(SizeofNode):
pass pass
class ConsumeNode(ExprNode):
# Consume expression
#
# operand ExprNode
subexprs = ['operand']
generate_runtime_check = True
operand_is_named = True
def infer_type(self, env):
operand_type = self.operand.infer_type(env)
if operand_type.is_cyp_class:
return PyrexTypes.cyp_class_qualified_type(operand_type, 'iso~')
else:
return operand_type
def analyse_types(self, env):
self.operand = self.operand.analyse_types(env)
operand_type = self.operand.type
if not operand_type.is_cyp_class:
error(self.pos, "Can only consume cypclass")
self.type = PyrexTypes.error_type
return self
if self.operand.is_name or self.operand.is_attribute:
self.is_temp = self.operand_is_named = True
# We steal the reference of the operand.
self.use_managed_ref = False
if operand_type.is_qualified_cyp_class:
if operand_type.qualifier == 'iso!':
error(self.pos, "Cannot consume iso!")
self.type = PyrexTypes.error_type
return self
self.generate_runtime_check = operand_type.qualifier not in ('iso', 'iso~')
if operand_type.qualifier == 'iso~':
self.type = operand_type
else:
self.type = PyrexTypes.cyp_class_qualified_type(operand_type.qual_base_type, 'iso~')
else:
self.type = PyrexTypes.cyp_class_qualified_type(operand_type, 'iso~')
return self
def may_be_none(self):
return self.operand.may_be_none()
def is_simple(self):
return self.operand.is_simple()
def make_owned_reference(self, code):
# We steal the reference of the consumed operand.
pass
def calculate_result_code(self):
if self.generate_runtime_check:
# TODO: generate runtime check for isolation
return self.operand.result()
else:
return self.operand.result()
def generate_result_code(self, code):
if self.is_temp:
operand_result = self.operand.result()
code.putln("%s = %s;" % (self.result(), operand_result))
if self.generate_runtime_check:
# TODO: generate runtime check for isolation
pass
# We steal the reference of the operand.
code.putln("%s = NULL;" % operand_result)
if self.operand.is_temp:
# TODO: steal the reference of the operand instead
code.put_incref(self.result(), self.type)
class TypeidNode(ExprNode): class TypeidNode(ExprNode):
# C++ typeid operator applied to a type or variable # C++ typeid operator applied to a type or variable
# #
...@@ -11634,7 +11710,7 @@ class BinopNode(ExprNode): ...@@ -11634,7 +11710,7 @@ class BinopNode(ExprNode):
else: else:
self.operand1 = self.operand1.coerce_to(func_type.args[0].type, env) self.operand1 = self.operand1.coerce_to(func_type.args[0].type, env)
self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env) self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env)
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
def result_type(self, type1, type2, env): def result_type(self, type1, type2, env):
if self.is_pythran_operation_types(type1, type2, env): if self.is_pythran_operation_types(type1, type2, env):
...@@ -13330,7 +13406,7 @@ class PrimaryCmpNode(ExprNode, CmpNode): ...@@ -13330,7 +13406,7 @@ class PrimaryCmpNode(ExprNode, CmpNode):
else: else:
self.operand1 = self.operand1.coerce_to(func_type.args[0].type, env) self.operand1 = self.operand1.coerce_to(func_type.args[0].type, env)
self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env) self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env)
self.type = func_type.return_type self.type = func_type.return_type.as_returned_type()
def analyse_memoryviewslice_comparison(self, env): def analyse_memoryviewslice_comparison(self, env):
have_none = self.operand1.is_none or self.operand2.is_none have_none = self.operand1.is_none or self.operand2.is_none
......
...@@ -309,6 +309,8 @@ def _p_factor(s): ...@@ -309,6 +309,8 @@ def _p_factor(s):
return p_typecast(s) return p_typecast(s)
elif sy == 'IDENT' and s.systring == "sizeof": elif sy == 'IDENT' and s.systring == "sizeof":
return p_sizeof(s) return p_sizeof(s)
elif sy == 'IDENT' and s.systring == "consume":
return p_consume(s)
return p_power(s) return p_power(s)
def p_typecast(s): def p_typecast(s):
...@@ -357,6 +359,13 @@ def p_sizeof(s): ...@@ -357,6 +359,13 @@ def p_sizeof(s):
s.expect(')') s.expect(')')
return node return node
def p_consume(s):
# s.sy == ident "consume"
pos = s.position()
s.next()
operand = p_factor(s)
return ExprNodes.ConsumeNode(pos, operand = operand)
def p_yield_expression(s): def p_yield_expression(s):
# s.sy == "yield" # s.sy == "yield"
...@@ -2535,7 +2544,7 @@ def p_c_simple_base_type(s, self_flag, nonempty, templates = None): ...@@ -2535,7 +2544,7 @@ def p_c_simple_base_type(s, self_flag, nonempty, templates = None):
base_type=base_type, is_const=is_const, is_volatile=is_volatile) base_type=base_type, is_const=is_const, is_volatile=is_volatile)
# Handle cypclass qualifiers # Handle cypclass qualifiers
if s.sy == 'IDENT' and s.systring in ('active',): if s.sy == 'IDENT' and s.systring in ('active', 'iso'):
qualifier = s.systring qualifier = s.systring
s.next() s.next()
base_type = p_c_base_type(s, self_flag=self_flag, nonempty=nonempty, templates=templates) base_type = p_c_base_type(s, self_flag=self_flag, nonempty=nonempty, templates=templates)
......
...@@ -228,6 +228,9 @@ class PyrexType(BaseType): ...@@ -228,6 +228,9 @@ class PyrexType(BaseType):
# Coerces array and C function types into pointer type for use as # Coerces array and C function types into pointer type for use as
# a formal argument type. # a formal argument type.
# #
# as_returned_type():
# Potentially annotates function call return types as pure rvalues.
#
is_pyobject = 0 is_pyobject = 0
is_unspecified = 0 is_unspecified = 0
...@@ -316,6 +319,9 @@ class PyrexType(BaseType): ...@@ -316,6 +319,9 @@ class PyrexType(BaseType):
def as_argument_type(self): def as_argument_type(self):
return self return self
def as_returned_type(self):
return self
def is_complete(self): def is_complete(self):
# A type is incomplete if it is an unsized array, # A type is incomplete if it is an unsized array,
# a struct whose attributes are not defined, etc. # a struct whose attributes are not defined, etc.
...@@ -648,6 +654,12 @@ class CheckedResultType(BaseType): ...@@ -648,6 +654,12 @@ class CheckedResultType(BaseType):
def as_argument_type(self): def as_argument_type(self):
return self.checked_base_type.as_argument_type() return self.checked_base_type.as_argument_type()
def as_returned_type(self):
returned_base_type = self.checked_base_type.as_returned_type()
if returned_base_type.same_as(self.checked_base_type):
return self
return CheckedResultType(self.pos, returned_base_type)
def cast_code(self, expr_code): def cast_code(self, expr_code):
return self.checked_base_type.cast_code(expr_code) return self.checked_base_type.cast_code(expr_code)
...@@ -1265,6 +1277,9 @@ class BufferType(BaseType): ...@@ -1265,6 +1277,9 @@ class BufferType(BaseType):
def as_argument_type(self): def as_argument_type(self):
return self return self
def as_returned_type(self):
return self
def specialize(self, values): def specialize(self, values):
dtype = self.dtype.specialize(values) dtype = self.dtype.specialize(values)
if dtype is not self.dtype: if dtype is not self.dtype:
...@@ -1956,6 +1971,9 @@ class QualifiedMethodType(BaseType): ...@@ -1956,6 +1971,9 @@ class QualifiedMethodType(BaseType):
def as_argument_type(self): def as_argument_type(self):
return c_ptr_type(self) return c_ptr_type(self)
def as_returned_type(self):
return self
# All that follows is just to behave exactly like the underlying function type # All that follows is just to behave exactly like the underlying function type
def __getattr__(self, name): def __getattr__(self, name):
...@@ -3000,6 +3018,13 @@ class CReferenceType(BaseType): ...@@ -3000,6 +3018,13 @@ class CReferenceType(BaseType):
else: else:
return type(self)(base_type) return type(self)(base_type)
def as_returned_type(self):
returned_base_type = self.ref_base_type.as_returned_type()
if returned_base_type == self.ref_base_type:
return self
else:
return CReferenceType(returned_base_type)
def deduce_template_params(self, actual): def deduce_template_params(self, actual):
return self.ref_base_type.deduce_template_params(actual) return self.ref_base_type.deduce_template_params(actual)
...@@ -4618,6 +4643,8 @@ class CypClassType(CppClassType): ...@@ -4618,6 +4643,8 @@ class CypClassType(CppClassType):
def assignable_from_resolved_type(self, other_type): def assignable_from_resolved_type(self, other_type):
if other_type.is_const_cyp_class: if other_type.is_const_cyp_class:
return 0 return 0
if other_type.is_qualified_cyp_class and other_type.qualifier != 'iso~':
return 0
if other_type.is_ptr and other_type.base_type.is_cpp_class and other_type.base_type.is_subclass(self) or other_type.is_null_ptr: if other_type.is_ptr and other_type.base_type.is_cpp_class and other_type.base_type.is_subclass(self) or other_type.is_null_ptr:
return 1 return 1
return super(CypClassType, self).assignable_from_resolved_type(other_type) return super(CypClassType, self).assignable_from_resolved_type(other_type)
...@@ -4733,6 +4760,9 @@ class ConstCypclassType(BaseType): ...@@ -4733,6 +4760,9 @@ class ConstCypclassType(BaseType):
def as_argument_type(self): def as_argument_type(self):
return self return self
def as_returned_type(self):
return self
def deduce_template_params(self, actual): def deduce_template_params(self, actual):
return self.const_base_type.deduce_template_params(actual) return self.const_base_type.deduce_template_params(actual)
...@@ -4772,13 +4802,20 @@ class ConstCypclassType(BaseType): ...@@ -4772,13 +4802,20 @@ class ConstCypclassType(BaseType):
class QualifiedCypclassType(BaseType): class QualifiedCypclassType(BaseType):
"A qualified cypclass reference" "A qualified cypclass reference"
# qualifier string the qualifier keyword # qualifier string the qualifier keyword: ('active' | 'iso' | 'iso~' | 'iso!' )
subtypes = ['qual_base_type'] subtypes = ['qual_base_type']
is_cyp_class = 1 is_cyp_class = 1
is_qualified_cyp_class = 1 is_qualified_cyp_class = 1
assignable_to = {
'active': ('active', 'iso~'),
'iso': ('iso~',),
'iso~': (),
'iso!': (),
}
def __init__(self, base_type, qualifier): def __init__(self, base_type, qualifier):
assert base_type.is_cyp_class assert base_type.is_cyp_class
self.qual_base_type = base_type self.qual_base_type = base_type
...@@ -4830,6 +4867,11 @@ class QualifiedCypclassType(BaseType): ...@@ -4830,6 +4867,11 @@ class QualifiedCypclassType(BaseType):
def as_argument_type(self): def as_argument_type(self):
return self return self
def as_returned_type(self):
if self.qualifier == 'iso':
return QualifiedCypclassType(self.qual_base_type, 'iso~')
return self
def deduce_template_params(self, actual): def deduce_template_params(self, actual):
return self.qual_base_type.deduce_template_params(actual) return self.qual_base_type.deduce_template_params(actual)
...@@ -4848,7 +4890,7 @@ class QualifiedCypclassType(BaseType): ...@@ -4848,7 +4890,7 @@ class QualifiedCypclassType(BaseType):
return self.assignable_from_resolved_type(src_type.resolve()) return self.assignable_from_resolved_type(src_type.resolve())
def assignable_from_resolved_type(self, src_type): def assignable_from_resolved_type(self, src_type):
if src_type.is_qualified_cyp_class and self.qualifier == src_type.qualifier: if src_type.is_qualified_cyp_class and src_type.qualifier in self.assignable_to[self.qualifier]:
return self.qual_base_type.assignable_from_resolved_type(src_type.qual_base_type) return self.qual_base_type.assignable_from_resolved_type(src_type.qual_base_type)
return 0 return 0
......
...@@ -3272,6 +3272,8 @@ class QualifiedCypclassScope(Scope): ...@@ -3272,6 +3272,8 @@ class QualifiedCypclassScope(Scope):
def qualified_cypclass_scope(base_type_scope, qualifier): def qualified_cypclass_scope(base_type_scope, qualifier):
if qualifier == 'active': if qualifier == 'active':
return ActiveCypclassScope(base_type_scope) return ActiveCypclassScope(base_type_scope)
elif qualifier.startswith('iso'):
return IsoCypclassScope(base_type_scope)
else: else:
return QualifiedCypclassScope(base_type_scope, qualifier) return QualifiedCypclassScope(base_type_scope, qualifier)
...@@ -3293,6 +3295,61 @@ class ActiveCypclassScope(QualifiedCypclassScope): ...@@ -3293,6 +3295,61 @@ class ActiveCypclassScope(QualifiedCypclassScope):
return base_entry.active_entry return base_entry.active_entry
class IsoCypclassScope(QualifiedCypclassScope):
def __init__(self, base_type_scope):
QualifiedCypclassScope.__init__(self, base_type_scope, 'iso')
def adapt(self, fieldtype, qualifier='iso'):
if fieldtype.is_qualified_cyp_class:
# attribute is sendable (either 'iso' or 'active')
return fieldtype
elif fieldtype.is_cyp_class:
# viewpoint adaptation
return PyrexTypes.cyp_class_qualified_type(fieldtype, qualifier)
else:
return fieldtype
def adapt_arg_type(self, arg):
arg = copy.copy(arg)
arg.type = self.adapt(arg.type)
return arg
def adapt_method_entry(self, base_entry):
iso_method_type = copy.copy(base_entry.type)
# The return type should be treated as 'iso' but cannot be consumed
return_type = self.adapt(base_entry.type.return_type, qualifier='iso!')
iso_method_type.return_type = return_type
iso_method_type.args = [self.adapt_arg_type(arg) for arg in base_entry.type.args]
if hasattr(base_entry.type, 'op_arg_struct'):
iso_method_type.op_arg_struct = entry.type.op_arg_struct
entry = copy.copy(base_entry)
entry.type = iso_method_type
return entry
def resolve(self, name):
base_entry = self.base_type_scope.lookup_here(name)
if base_entry is None:
return None
if base_entry.is_type:
return base_entry
if base_entry.is_cfunction:
iso_alternatives = []
for e in base_entry.all_alternatives():
iso_entry = self.adapt_method_entry(e)
iso_alternatives.append(iso_entry)
iso_entry.overloaded_alternatives = iso_alternatives
return iso_alternatives[0]
else:
base_entry_type = base_entry.type
adapted_type = self.adapt(base_entry_type)
if adapted_type is base_entry_type:
return base_entry
else:
entry = copy.copy(base_entry)
entry.type = adapted_type
return entry
class TemplateScope(Scope): class TemplateScope(Scope):
def __init__(self, name, outer_scope): def __init__(self, name, outer_scope):
Scope.__init__(self, name, outer_scope, None) Scope.__init__(self, name, outer_scope, None)
......
...@@ -538,6 +538,8 @@ def simply_type(result_type, pos): ...@@ -538,6 +538,8 @@ def simply_type(result_type, pos):
result_type = result_type.ref_base_type result_type = result_type.ref_base_type
if result_type.is_cv_qualified: if result_type.is_cv_qualified:
result_type = result_type.cv_base_type result_type = result_type.cv_base_type
if result_type.is_qualified_cyp_class and result_type.qualifier == 'iso~':
result_type = PyrexTypes.cyp_class_qualified_type(result_type.qual_base_type, 'iso')
if result_type.is_cpp_class: if result_type.is_cpp_class:
result_type.check_nullary_constructor(pos) result_type.check_nullary_constructor(pos)
if result_type.is_array: if result_type.is_array:
......
...@@ -439,7 +439,7 @@ ...@@ -439,7 +439,7 @@
/* /*
* Check whether a CyObject is an instance of a given type. * Check whether a CyObject is an instance of a given type.
* *
* template: * template:
* - T: the type * - T: the type
*/ */
...@@ -463,7 +463,7 @@ ...@@ -463,7 +463,7 @@
* Cast from CyObject to PyObject: * Cast from CyObject to PyObject:
* - borrow an atomic reference * - borrow an atomic reference
* - return a new Python reference * - return a new Python reference
* *
* Note: an optimisation could be to steal a reference but only decrement * Note: an optimisation could be to steal a reference but only decrement
* when Python already has a reference, because calls to this function * when Python already has a reference, because calls to this function
* are likely (certain even?) to be followed by a Cy_DECREF; stealing the * are likely (certain even?) to be followed by a Cy_DECREF; stealing the
...@@ -488,11 +488,11 @@ ...@@ -488,11 +488,11 @@
* Cast from PyObject to CyObject: * Cast from PyObject to CyObject:
* - borrow an Python reference * - borrow an Python reference
* - return a new atomic reference * - return a new atomic reference
* *
* In case of conversion failure: * In case of conversion failure:
* - raise an exception * - raise an exception
* - return NULL * - return NULL
* *
* template: * template:
* - U: the type of the underlying cypclass * - U: the type of the underlying cypclass
*/ */
...@@ -791,7 +791,7 @@ void CyLock::unwlock() { ...@@ -791,7 +791,7 @@ void CyLock::unwlock() {
/* /*
* Atomic counter increment and decrement implementation based on * Atomic counter increment and decrement implementation based on
* @source: https://www.boost.org/doc/libs/1_73_0/doc/html/atomic/usage_examples.html * @source: https://www.boost.org/doc/libs/1_73_0/doc/html/atomic/usage_examples.html
*/ */
void CyObject::CyObject_INCREF() const void CyObject::CyObject_INCREF() const
{ {
this->nogil_ob_refcnt.fetch_add(1, std::memory_order_relaxed); this->nogil_ob_refcnt.fetch_add(1, std::memory_order_relaxed);
......
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