On 6/20/26 2:05 PM, Arsen Arsenović wrote:
Jason Merrill <[email protected]> writes:

I was surprised that no change to standard_conversion was needed, but I
guess the change to comp_ptr_ttypes_real covers that.

(apparently not yet)

I've now fixed this in my version of the patch.

ck_qual vs ck_ptr is a bit of a question, whether these conversions
should fit into the C++ conversion model as a qualification conversion
or pointer conversion.  I think ck_qual is the better fit, since they
don't change the pointed-to type.  So we want to get ck_qual for
(valid) implicit conversions as well as casts.

Given that, I think handling this in comp_ptr_ttypes_real still makes
sense, rather than adding an additional case in standard_conversion.
It seems that hunk just isn't right yet.

Yes, after experimenting with both approaches, I think this is right in
the end.

How do we (properly) request an
'ADDR_SPACE_CONVERT_EXPR' to be generated, be it indeed via
'cp_fold_convert' or something else?

For a ck_qual, convert_like_internal ends up calling cp_convert ->
ocp_convert -> cp_convert_to_pointer.  It seems this last function
needs updating to not assume it can express all conversions between
pointers to the same type with build_nop.

We actually seem to need to make a far greater change than just a change
to this one function.  For instance, reinterpret_casts do not go through
this mechanism for pointers to void or object types, instead just
building a NOP_EXPR directly (due to the TYPE_PTROBV_P (type) &&
TYPE_PTROBV_P (intype) branch).

The FE relies on NOP_EXPR also to represent reinterpret casts at least
in templates, AFAICT.  This means that any code building pointer casts
would also be complicated by processing_template_decl.

It seems to be that a cleaner solution is to extend NOP_EXPR to be able
to handle address space conversions in the C++ frontend, by lowering
such NOP_EXPRs into an ADDR_SPACE_CONVERT_EXPR of a NOP_EXPR during
genericization.  If you think this is a bad idea, I can go back and fix
it up.  But, it did allow me to continue prototyping (and will unblock
Thomas in getting around to the actual work he needs this feature for),
so it's a decent stopgap.

Hmm, seems reasonable.

Adjusted patch below, for reference.  It is definitely not complete, not
in the least because of the TODOs left therein.

I haven't tested it fully, but it passes the Clang testcase
test/SemaCXX/address-space-conversion.cpp (which I am yet to port into
dg syntax).  It is not a very extensive testcase, as it lacks coverage
of any case where there exist a pair of address spaces A and B such that
A ⊃ B but not B ⊃ A.  I've tried to mitigate this issue with a
self-test.

I'm also not yet sure whether the template deduction logic here is
correct.  Nor whether compare_ics behaves as it should.
 check_cv_quals_for_unify (int strict, tree arg, tree parm)
 {
-  int arg_quals = cp_type_quals (arg);
-  int parm_quals = cp_type_quals (parm);
+  int arg_quals = CLEAR_QUAL_ADDR_SPACE (cp_type_quals (arg));
+  int parm_quals = CLEAR_QUAL_ADDR_SPACE (cp_type_quals (parm));
+
+  /*  Try to unify ARG's address space into PARM's address space.
+      If PARM does not have any address space qualifiers (ie., as_parm is 0),
+      there are no constraints on address spaces for this type.  */
+  addr_space_t as_arg = DECODE_QUAL_ADDR_SPACE (cp_type_quals (arg));
+  addr_space_t as_parm = DECODE_QUAL_ADDR_SPACE (cp_type_quals (parm));
+  addr_space_t as_common;
+  addr_space_superset (as_arg, as_parm, &as_common);
if (TREE_CODE (parm) == TEMPLATE_TYPE_PARM
       && !(strict & UNIFY_ALLOW_OUTER_MORE_CV_QUAL))
@@ -25853,6 +25861,9 @@ check_cv_quals_for_unify (int strict, tree arg, tree 
parm)
        return 0;
     }
+ if (!(as_parm == as_common || as_parm == 0))
+    return 0;

The address space logic needs to respect the 'strict' flags like the rest of the function. So we want to allow as_parm == 0 iff when UNIFY_ALLOW_LESS_CV_QUAL, or as_arg == 0 if UNIFY_ALLOW_MORE_CV_QUAL.

And I don't think we want to consider superset here, just compare as_parm == as_arg. Deduction is stricter than conversion.

@@ -719,8 +720,28 @@ composite_pointer_type_r (const op_location_t &location,
        return error_mark_node;
       result_type = void_type_node;
     }
-  const int q1 = cp_type_quals (pointee1);
-  const int q2 = cp_type_quals (pointee2);
+
+  /* If we reach this point, and have different address spaces, that means that
+     the difference between address spaces is too "deep" to cross (e.g. AS1 T**
+     vs. AS2 T** - note the double star).  */
+  auto q1 = cp_type_quals (pointee1);
+  auto q2 = cp_type_quals (pointee2);
+  addr_space_t as_t1 = DECODE_QUAL_ADDR_SPACE (q1);
+  addr_space_t as_t2 = DECODE_QUAL_ADDR_SPACE (q2);
+
+  if (as_t1 != as_t2)
+    {
+      if (complain & tf_error)
+       composite_pointer_error (location, diagnostics::kind::permerror,
+                                t1, t2, operation);
+      else
+       return error_mark_node;

We might invert this 'if' and move the error out of it...

+      /* Pick an arbitrary AS and stick to it.  */
+      q1 = SET_QUAL_ADDR_SPACE (q1, as_t1);
+      q2 = SET_QUAL_ADDR_SPACE (q2, as_t1);

...since this is a continuation of the error path.

@@ -26477,10 +26488,28 @@ unify (tree tparms, tree targs, tree parm, tree arg, 
int strict,
                                         arg, parm))
            return unify_cv_qual_mismatch (explain_p, parm, arg);
+ int arg_cv_quals = cp_type_quals (arg);
+         int parm_cv_quals = cp_type_quals (parm);
+
+         /* If PARM does not contain any address spaces constraints it can
+            fully match the address space of ARG.  However, if PARM contains an
+            address space constraints, it becomes the upper bound.  That is,
+            AS_ARG may be promoted to AS_PARM but not the converse.  If we
+            ended up here, it means that `check_cv_quals_for_unify' succeeded
+            and that either AS_PARM is 0 (ie., no constraints) or AS_COMMON ==
+            AS_PARM.  */
+         addr_space_t as_arg = DECODE_QUAL_ADDR_SPACE (arg_cv_quals);
+         addr_space_t as_parm = DECODE_QUAL_ADDR_SPACE (parm_cv_quals);
+         addr_space_t as_common = as_parm ? 0 : as_arg;
+
          /* Consider the case where ARG is `const volatile int' and
             PARM is `const T'.  Then, T should be `volatile int'.  */
          arg = cp_build_qualified_type
            (arg, cp_type_quals (arg) & ~cp_type_quals (parm), tf_none);

This line should be removed...

+         int unified_cv =
+           (CLEAR_QUAL_ADDR_SPACE (arg_cv_quals & ~parm_cv_quals)
+           | ENCODE_QUAL_ADDR_SPACE (as_common));
+         arg = cp_build_qualified_type (arg, unified_cv, tf_none);

...in favor of this one.

@@ -11963,6 +12126,7 @@ comp_ptr_ttypes_real (tree to, tree from, int constp)
          if (!at_least_as_qualified_p (from, to))
            {
              if (constp == 0)
+               /* I'm not sure what this check is meant to be covering.  */
                return false;
Under https://eel.is/c++draft/conv#qual you can add cv-quals on an inner level if the outer levels are all const, e.g. int** -> int const* const*. If constp == 0, the outer levels aren't all const, so adding cv-quals is not allowed.

I don't think we want to extend that rule to address spaces, so your is_toplevel seems right.

Feel free to add a comment.

@@ -11952,6 +12101,20 @@ comp_ptr_ttypes_real (tree to, tree from, int constp)
                           TYPE_OFFSET_BASETYPE (to)))
        return false;
+ /* We want to permit a toplevel address space change, but only into a
+        subset address space.  TODO(arsen): this change has an effect on
+        compare_ics through comp_cv_qual_signature, but I have no idea what
+        that effect is yet.  */
+      if (auto as_to = TYPE_ADDR_SPACE (to), as_from =  TYPE_ADDR_SPACE (from);

init-statements are a C++17 feature and we're still limited to C++14.

+         as_to != as_from)
+       {
+         if (!is_toplevel)
+           return false;
+
+         if (!targetm.addr_space.subset_p (as_from, as_to))

This doesn't handle the constp == -1 case (which is gratuitously obscure, the parameter should really be a bool called something like "proper" and constp be a local variable) where we want to return false if each is a subset of the other.

Jason

Reply via email to