[RFC] Improved address space conversion semantics for targets and language dialects

== Introduction ==

During the work that Anastasia has been doing on enabling OpenCL C++,
points have been raised about the state of address space support in
Clang. Currently, this support is rather ad-hoc. The representation of
address spaces in qualifiers and the lowering of Clang address spaces
to their LLVM counterparts are sound, but the behavioral semantics of
address spaces given by the Embedded C TR are not really sufficient to
model address space behaviors for arbitrary target architectures.

Here are some of the reviews in which this has come up:

Many address space semantics are locked behind the OpenCL language
option, even though those semantics would likely be applicable to
non-OpenCL cases as well. This means that, when not using any
particular address space-using language dialect, the address space
semantics are far too loosely defined. When using address spaces
outside of the ones defined in LangAS (the ‘target’ address spaces),
you can convert between any two address spaces explicitly, even though
this might not make sense on a particular target. There is no way for a
target to define which address spaces are compatible with each other.

Technically, this behavior is in accordance with the Embedded-C TR
(explicitly converting between all address spaces is allowed, but
undefined if they aren’t compatible), but I do not believe this
behavior is meaningful. If a target’s address spaces are disjoint,
there is no reason to let a user convert between them, even with a
cast.

In order to make the support for address spaces more complete, general
and also useful for targets with a need to define more specific rules
for their address spaces, a generalization of the conversion semantics
for address spaces is needed.

== Current implementation ==

Currently, address space compatibility is defined in terms of
superspaces. An address space can encompass others, in which case it
would be considered a superset/superspace of the other address spaces.

Given two address spaces, Super and Sub, where Super is a superspace of
Sub, then it is valid to implicitly convert a Sub T* to a Super T*,
as all pointers to Sub are encompassed by pointers to Super. It is not
necessarily safe to implicitly convert in the other direction. Also, an
address space is a superspace of itself.

This is currently implemented in Qualifiers::isAddressSpaceSupersetOf.
The OpenCL __generic address space is the superspace of all other
address spaces except for the OpenCL __constant address space. This
method is used when checking pointer compatibility during assignment
(and other forms of initialization).

Explicitly converting (casting) between two address spaces is permitted
if either of them is a superspace of the other. This is implemented in
Qualifiers::isAddressSpaceOverlapping. This check is only done in
OpenCL mode; when using address spaces in regular C, explicit
conversion is always permitted.

== Issues ==

The problem with modeling the address space conversion semantics on
superspaces and subspaces is that these two concepts are orthogonal. A
target or language could have address spaces which ‘overlap’ in some
way, yet disallow implicit or explicit conversion between them. It
could also have address spaces which do not overlap, but for which
explicit or implicit conversion is permitted.

== Suggestion ==

The suggestion in this RFC to improve the way targets and languages in
Clang can express the semantics of address space conversions is to add
a mechanism to ASTContext and TargetInfo which lets us query if a
conversion from one address space to another address space is either:

  • invalid
  • valid implicitly
  • valid explicitly

A suggestion for the interface on ASTContext would be

bool isAddressSpaceConvertible(LangAS From, LangAS To, bool Explicit)

The method would initially consult any language address space
conversion rules (such as conversion rules in OpenCL), and if no such
rules apply, proceed to fall back on a TargetInfo hook.

The TargetInfo hook would have the same format as the ASTContext
method, but would return the validity of the conversion for the
particular compilation target. The default behavior of this hook would
be that all implicit address space conversions are disallowed, and all
explicit conversions are permitted.

(An alternative setup here would be that the ASTContext method queries
the TargetInfo directly, and have the language semantics be defined in
the TargetInfo base method instead. This would let targets override
language semantics. I don’t know if this is necessary, or desirable.)

It’s important to point out that implicit validity should imply the
explicit one. If a call to this ASTContext method is made as below, and
returns true:
Ctx.isAddressSpaceConvertible(From, To, false)
then the following should also return true:
Ctx.isAddressSpaceConvertible(From, To, true)

If it did not, then To T* p = from_ptr would be permitted, but
To T* p = (To T*)from_ptr would not be, which is rather
counterintuitive.

== Necessary work ==

The steps to implement this RFC should be as follows:

  • A patch which adds the aforementioned methods to ASTContext and
    TargetInfo, and defines the necessary semantics for the default and
    language-specific conversions in them.

  • A patch which replaces the currently used methods for address space
    compatibility mentioned earlier (isAddressSpaceSupersetOf,
    isAddressSpaceOverlapping) with calls to the new methods in ASTContext.

There are some other users of these methods, such as
Qualifiers::compatiblyIncludes, but it’s not entirely clear how to
update these as a Qualifiers does not have access to ASTContext.

  • Possibly a patch to remove the old address space compatibility
    methods, if it can be determined that they are no longer needed.

Thank you for reading!

The problem with modeling the address space conversion semantics on
superspaces and subspaces is that these two concepts are orthogonal. A
target or language could have address spaces which 'overlap' in some
way, yet disallow implicit or explicit conversion between them. It
could also have address spaces which do not overlap, but for which
explicit or implicit conversion is permitted.

I am trying to understand how this could work. I think the current definition
of overlapping in embedded C TC expresses logical overlapping, but not
necessary physical one. My understanding is that if address spaces overlap
logically they can be converted explicitly in both directions and might be
converted implicitly (depending on whether one address space is a superset
of another). Logical overlapping can imply that memory segments physically
overlap but might not. In the latter case it's just a logical concept to simplify
programming or compiler implementation. That's how we are using generic
address space in OpenCL for example, that isn't a physical memory segment.
So I am trying to understand why something that doesn’t overlap (either
logically or physically) would still be convertible? I just found the current logic
with overlapping quite useful in various places for C++ (i.e. overload resolution
where subset is preferable to superset) and it might have wider implications in
case we are to change those.

Also (may be it belongs to a separate discussion though) for C++ specifically
generic address space becomes really key because it's used for implementing
hidden 'this' parameter/expression. I am not quite convinced the current
semantic of it taken from C is sufficient. Because it isn't the same as default
address space where implementation decides to put objects by default but it is
an address space to which every other should be allowed to convert, unless
there is a good reason not to (i.e. logical superset of all or most of the other
address spaces). If there isn't such address space... the application developer
will be forced to write all the implicit operations/methods for each address space
in which a class variable can be declared. It is quite impractical, especially if
there is no special logic needed for different address spaces!

The method would initially consult any language address space
conversion rules (such as conversion rules in OpenCL), and if no such
rules apply, proceed to fall back on a TargetInfo hook.

The TargetInfo hook would have the same format as the ASTContext
method, but would return the validity of the conversion for the
particular compilation target. The default behavior of this hook would
be that all implicit address space conversions are disallowed, and all
explicit conversions are permitted.

(An alternative setup here would be that the ASTContext method queries
the TargetInfo directly, and have the language semantics be defined in
the TargetInfo base method instead. This would let targets override
language semantics. I don't know if this is necessary, or desirable.)

I would vote against target rules ever overriding language ones. This
reduces portability of code among targets which defeats the purpose of
the language mode in my view.

The first idea makes sense to me i.e. use target rules if any of address
spaces is larger than FirstTargetAddressSpace otherwise language rules
should be used.

* A patch which replaces the currently used methods for address space
compatibility mentioned earlier (isAddressSpaceSupersetOf,
isAddressSpaceOverlapping) with calls to the new methods in ASTContext.

There are some other users of these methods, such as
Qualifiers::compatiblyIncludes, but it's not entirely clear how to
update these as a Qualifiers does not have access to ASTContext.

Wondering if it could migrate to ASTContext or it can take ASTContext as
parameter. Although both might cause the layering violations. :frowning:

* Possibly a patch to remove the old address space compatibility
methods, if it can be determined that they are no longer needed.

I would prefer to migrate to the new implementation completely
instead of maintaining 2 approaches. We just need to make sure that the
new logic accommodates the old one with the new functionality if needed.
This should work and makes code base more readable and maintainable.
However, if it's not possible to do this directly at the start we can gradually
replace it.

Overall, this work would be a good step forward towards better upstream
support for embedded and heterogeneous devices. One extra thought I have
(again it might belong to a separate RCF) is whether providing some way to
define address space compatibility rules using some sort of syntax in a language
makes sense? The application of this would be (a) portability of code across
different compilers (b) portability of code among accelerators in the same domain
(i.e. ML, Graphics, ...).

Thanks,
Anastasia

At least there is some value allowing explicit conversion in general
between address spaces to do some basic type erasure. Otherwise it is
difficult to implement things like C++ std::any or whatever.
Of course at the end you go back to the original address space after a
journey though the type erased address space

AS1 → AS_erased → AS1.

Thank you for having started this discussion. It is time to unify our
efforts through the various users of address spaces.

At least there is some value allowing explicit conversion in general
between address spaces to do some basic type erasure. Otherwise it is
difficult to implement things like C++ std::any or whatever.
Of course at the end you go back to the original address space after a
journey though the type erased address space

AS1 → AS_erased → AS1.

I am not sure erasing address space information in the example of

std::any is a good idea as it seems it might violate type safety.

I haven’t looked into details yet, perhaps I should. How does it currently

work with other qualifiers btw? In the meantime if you have any code

example where you find removing the address space useful for the case

you have highlighted or any similar one, it would be valuable input for this

discussion.

Thanks!

Anastasia

Thank you for the feedback!

The problem with modeling the address space conversion semantics on
superspaces and subspaces is that these two concepts are orthogonal. A
target or language could have address spaces which 'overlap' in some
way, yet disallow implicit or explicit conversion between them. It
could also have address spaces which do not overlap, but for which
explicit or implicit conversion is permitted.

I am trying to understand how this could work. I think the current definition
of overlapping in embedded C TC expresses logical overlapping, but not
necessary physical one. My understanding is that if address spaces overlap
logically they can be converted explicitly in both directions and might be
converted implicitly (depending on whether one address space is a superset
of another). Logical overlapping can imply that memory segments physically
overlap but might not. In the latter case it's just a logical concept to simplify
programming or compiler implementation. That's how we are using generic
address space in OpenCL for example, that isn't a physical memory segment.
So I am trying to understand why something that doesn’t overlap (either
logically or physically) would still be convertible? I just found the current logic
with overlapping quite useful in various places for C++ (i.e. overload resolution
where subset is preferable to superset) and it might have wider implications in
case we are to change those.

It's true that the address space overlap semantics can be used for
conversion legality (that's how it's used today, after all) but in
pretty much all of the locations that use the overlapping/superset
accessors today, what we are actually interested in knowing is
conversion legality, even for things like overload resolution. If doing
such a conversion was not legal, then obviously we cannot consider an
overload to be viable, for example. None of the using code really seems
interested in knowing about address space overlap per se, so I don't
feel like it's the clearest way of asking for the relevant information.

For overloading, even a complex address space design like A( B( C ) ) )
doesn't really necessitate knowing that C is both a subset of A and B.
If you have two overloads, one for an A 'this' and another for a B
'this', and you try calling a method on a C T*, then it should simply be
ambiguous anyway, since there's two conversion sequences of equal rank
from the original C T*.

Also (may be it belongs to a separate discussion though) for C++ specifically
generic address space becomes really key because it's used for implementing
hidden 'this' parameter/expression. I am not quite convinced the current
semantic of it taken from C is sufficient. Because it isn't the same as default
address space where implementation decides to put objects by default but it is
an address space to which every other should be allowed to convert, unless
there is a good reason not to (i.e. logical superset of all or most of the other
address spaces). If there isn't such address space... the application developer
will be forced to write all the implicit operations/methods for each address space
in which a class variable can be declared. It is quite impractical, especially if
there is no special logic needed for different address spaces!

Well, depending on the address space semantics of a particular target,
the developer will be forced to do this anyway. It works in OpenCL
because of __generic, but there's no guarantee of there being a
'default/generic' address space to use for 'this' in an arbitrary target
or language.

I agree that could be a problem in regular C++. Wasn't there a proposal
for letting you template the method qualifiers somehow?

The method would initially consult any language address space
conversion rules (such as conversion rules in OpenCL), and if no such
rules apply, proceed to fall back on a TargetInfo hook.
The TargetInfo hook would have the same format as the ASTContext
method, but would return the validity of the conversion for the
particular compilation target. The default behavior of this hook would
be that all implicit address space conversions are disallowed, and all
explicit conversions are permitted.
(An alternative setup here would be that the ASTContext method queries
the TargetInfo directly, and have the language semantics be defined in
the TargetInfo base method instead. This would let targets override
language semantics. I don't know if this is necessary, or desirable.)

I would vote against target rules ever overriding language ones. This
reduces portability of code among targets which defeats the purpose of
the language mode in my view.

The first idea makes sense to me i.e. use target rules if any of address
spaces is larger than FirstTargetAddressSpace otherwise language rules
should be used.

Yes, I agree with that.

* A patch which replaces the currently used methods for address space
compatibility mentioned earlier (isAddressSpaceSupersetOf,
isAddressSpaceOverlapping) with calls to the new methods in ASTContext.
There are some other users of these methods, such as
Qualifiers::compatiblyIncludes, but it's not entirely clear how to
update these as a Qualifiers does not have access to ASTContext.

Wondering if it could migrate to ASTContext or it can take ASTContext as
parameter. Although both might cause the layering violations. :frowning:

Yes, I feel like it would get messy if we have to start passing
ASTContexts into Qualifiers methods like that. Might as well just call
something on the ASTContext directly instead.

I think the approach would be to either
* remove the AS check from compatiblyIncludes and go through the uses to
determine if any of the callees care about address spaces and need to be
amended, or
* do as you suggest and move the method to ASTContext instead.

They both feel sort of invasive, though.

* Possibly a patch to remove the old address space compatibility
methods, if it can be determined that they are no longer needed.

I would prefer to migrate to the new implementation completely
instead of maintaining 2 approaches. We just need to make sure that the
new logic accommodates the old one with the new functionality if needed.
This should work and makes code base more readable and maintainable.
However, if it's not possible to do this directly at the start we can gradually
replace it.

Sure, we certainly shouldn't have the same thing implemented twice. I
simply meant that it might not be reasonable to replace/remove
everything in a single swoop, so eventually there would be a patch that
removes the old system.

Overall, this work would be a good step forward towards better upstream
support for embedded and heterogeneous devices. One extra thought I have
(again it might belong to a separate RCF) is whether providing some way to
define address space compatibility rules using some sort of syntax in a language
makes sense? The application of this would be (a) portability of code across
different compilers (b) portability of code among accelerators in the same domain
(i.e. ML, Graphics, ...).

I've been thinking about this as well. I'm not sure if I like the idea
of expressing it in the source, though. That would mean that for a
particular target or language, you'd always need to include a special
header with the AS definitions, which is a bit odd. It would be an
extension as well, so I'm not sure how portable across compilers it
would be either.

One idea I've been contemplating is a TableGen backend that lets you
define address space names, keywords and semantics as TableGen
definitions. Both definition kinds for languages and targets would
exist. Not sure if it's important enough to warrant a new backend, though.

/ Bevin

It's true that the address space overlap semantics can be used for
conversion legality (that's how it's used today, after all) but in
pretty much all of the locations that use the overlapping/superset
accessors today, what we are actually interested in knowing is
conversion legality, even for things like overload resolution. If doing
such a conversion was not legal, then obviously we cannot consider an
overload to be viable, for example. None of the using code really seems
interested in knowing about address space overlap per se, so I don't
feel like it's the clearest way of asking for the relevant information.

When we rank the overload with various address spaces we use this
logic - subsets are preferred to supersets. This could of course be
changed but this is one place where we actually use this logic. Not sure
if there are more. Apparently the concept fits other rules for qualifiers from
C++. May be the following comment can help to understand more:
https://reviews.llvm.org/D55850#inline-496966

I am just thinking this change might have bigger impact than it seems
originally. But I am not against it of we think it's more intuitive and can simplify
code base.

Also in general it really helps when implementation follows the logic from
specification. It is often the only way to reason about it. Documenting code
sufficiently has always been sensitive aspects. So if we are to switch to
different logic we should be prepared to provide enough documentation for the
developers.

Well, depending on the address space semantics of a particular target,
the developer will be forced to do this anyway. It works in OpenCL
because of __generic, but there's no guarantee of there being a
'default/generic' address space to use for 'this' in an arbitrary target
or language.

Yes, that's why I am wondering if generic address space should be
introduced in C++ as purely logical address space concept?

I agree that could be a problem in regular C++. Wasn't there a proposal
for letting you template the method qualifiers somehow?

There is this paper

P0847R1: Deducing this

but it has an issue with superfluous template instantiation problem
because the methods are templated on the full qualified type of 'this'
and not just address spaces.

http://lists.llvm.org/pipermail/cfe-dev/2018-December/060545.html

I would quite like to investigate some solution specific to the address
spaces. However, this approach still solves duplication at the source
level quite well.

I've been thinking about this as well. I'm not sure if I like the idea
of expressing it in the source, though. That would mean that for a
particular target or language, you'd always need to include a special
header with the AS definitions, which is a bit odd. It would be an
extension as well, so I'm not sure how portable across compilers it
would be either.

Do you think this can be simplified by the use of implicit headers in Clang?
This is not uncommon. We include OpenCL builtin function header implicitly
for example.

One idea I've been contemplating is a TableGen backend that lets you
define address space names, keywords and semantics as TableGen
definitions. Both definition kinds for languages and targets would
exist. Not sure if it's important enough to warrant a new backend, though.

Ok, that could work. However, it's still not portable across different compilers.

Cheers,
Anastasia

It's true that the address space overlap semantics can be used for
conversion legality (that's how it's used today, after all) but in
pretty much all of the locations that use the overlapping/superset
accessors today, what we are actually interested in knowing is
conversion legality, even for things like overload resolution. If doing
such a conversion was not legal, then obviously we cannot consider an
overload to be viable, for example. None of the using code really seems
interested in knowing about address space overlap per se, so I don't
feel like it's the clearest way of asking for the relevant information.

When we rank the overload with various address spaces we use this
logic - subsets are preferred to supersets. This could of course be
changed but this is one place where we actually use this logic. Not sure
if there are more. Apparently the concept fits other rules for qualifiers from
C++. May be the following comment can help to understand more:
⚙ D55850 [OpenCL] Allow address spaces as method qualifiers

Right... But will 'shorter' implicit AS conversions really be considered
better than 'longer' ones in this case? I could be wrong, but it doesn't
seem to me like the overload logic makes any considerations about which
AS conversion is better, just that not having to do an AS conversion is
better than doing one. OpenCL doesn't really have an example of this
kind of address space layout, so it's hard to simply test what the
behavior is.

If there was a '__sublocal' address space which was a subspace of
__local, and you had overloads for __generic and __local and a pointer
type of __sublocal, then it would still consider the overload to be
ambiguous, not prefer the __local overload.

Ultimately, 'isAddressSpaceSupersetOf' is mostly used to mean 'implicit
conversion between these ASes is permitted', even in the overload
resolution case. I find that it's easier to understand interfaces when
their name matches their usage, and that simply doesn't seem to be the
case for this one.

I am just thinking this change might have bigger impact than it seems
originally. But I am not against it of we think it's more intuitive and can simplify
code base.

Also in general it really helps when implementation follows the logic from
specification. It is often the only way to reason about it. Documenting code
sufficiently has always been sensitive aspects. So if we are to switch to
different logic we should be prepared to provide enough documentation for the
developers.

This is a very good point. It's certainly the case that both OpenCL and
Embedded-C express the AS compatibility in terms of the superset and
subset model.

We could change the new design to simply let targets specify which
address spaces are supersets, just like in today's model. However, I
find that this gets a bit confusing when dealing with address spaces
across targets and languages; if a target wants to encode some kind of
special OpenCL interoperability, it's a bit odd to say that 'my AS42 is
a subset of __generic' when in reality, they don't have anything to do
with each other. Or maybe it's not as strange as I feel it is?

It also means that explicit conversion would always be permitted if
implicit conversion is permitted in one of the directions. In other
words, targets wouldn't be able to configure explicit conversion for
their address spaces; the behavior would always be implied. This makes
it hard to keep the current C/C++ address space model (no implicit
conversions, all explicit conversions) in a more generic design. That
model only works currently because some code is locked behind the OpenCL
language option.

Well, depending on the address space semantics of a particular target,
the developer will be forced to do this anyway. It works in OpenCL
because of __generic, but there's no guarantee of there being a
'default/generic' address space to use for 'this' in an arbitrary target
or language.

Yes, that's why I am wondering if generic address space should be
introduced in C++ as purely logical address space concept?

If this would be a logical concept only, would it be implemented purely
on the Clang level and not via LLVM addrspaces? I'm not sure how the
realization of that address space would work out. For targets without
such an address space, would every use of a pointer to the generic
address space require a check to determine which 'subspace' the pointer
belongs to? Would targets have to enumerate their address spaces to
support this?

I agree that could be a problem in regular C++. Wasn't there a proposal
for letting you template the method qualifiers somehow?

There is this paper

  P0847R1: Deducing this

but it has an issue with superfluous template instantiation problem
because the methods are templated on the full qualified type of 'this'
and not just address spaces.

[cfe-dev] [RFC] Solution for preserving the address space of ‘this’ in C++ methods

I would quite like to investigate some solution specific to the address
spaces. However, this approach still solves duplication at the source
level quite well.

There's also issues like constructors with address spaces, and operator
new with address spaces... but perhaps those are better dealt with
another time.

I've been thinking about this as well. I'm not sure if I like the idea
of expressing it in the source, though. That would mean that for a
particular target or language, you'd always need to include a special
header with the AS definitions, which is a bit odd. It would be an
extension as well, so I'm not sure how portable across compilers it
would be either.

Do you think this can be simplified by the use of implicit headers in Clang?
This is not uncommon. We include OpenCL builtin function header implicitly
for example.

That is a viable idea, but I think that if we would go through the
trouble of including an implicit header containing
implementation-specific configuration, then we might as well encode it
into the target definitions and save on the header in the first place.

I think it makes more sense for a language extension like OpenCL as it
needs to encode those regardless of target, so having a reusable set of
definitions is good for that use case. But for a specific target, it's
less useful.

One idea I've been contemplating is a TableGen backend that lets you
define address space names, keywords and semantics as TableGen
definitions. Both definition kinds for languages and targets would
exist. Not sure if it's important enough to warrant a new backend, though.

Ok, that could work. However, it's still not portable across different compilers.

Yes, it's a bit unfortunate. Embedded-C (and other standards that define
AS support) make most of it implementation-defined, so I think finding a
method that works across different compilers is tricky...

Right... But will 'shorter' implicit AS conversions really be considered
better than 'longer' ones in this case? I could be wrong, but it doesn't
seem to me like the overload logic makes any considerations about which
AS conversion is better, just that not having to do an AS conversion is
better than doing one. OpenCL doesn't really have an example of this
kind of address space layout, so it's hard to simply test what the
behavior is.

If there was a '__sublocal' address space which was a subspace of
__local, and you had overloads for __generic and __local and a pointer
type of __sublocal, then it would still consider the overload to be
ambiguous, not prefer the __local overload.

Ultimately, 'isAddressSpaceSupersetOf' is mostly used to mean 'implicit
conversion between these ASes is permitted', even in the overload
resolution case. I find that it's easier to understand interfaces when
their name matches their usage, and that simply doesn't seem to be the
case for this one.

Yes, I believe we might not have any upstream example for this and
hence current implementation is not complete. However, the current
approach has some sort of the guidelines on how the ambiguity can
be resolved that we would need to figure out with the new logic.

I guess we could use the implicit/explicit conversion for this, however
it might become more convoluted.

This is a very good point. It's certainly the case that both OpenCL and
Embedded-C express the AS compatibility in terms of the superset and
subset model.

We could change the new design to simply let targets specify which
address spaces are supersets, just like in today's model. However, I
find that this gets a bit confusing when dealing with address spaces
across targets and languages; if a target wants to encode some kind of
special OpenCL interoperability, it's a bit odd to say that 'my AS42 is
a subset of __generic' when in reality, they don't have anything to do
with each other. Or maybe it's not as strange as I feel it is?

I think there might be a little bit of that. For the developers from embedded
C or OpenCL background this concept is quite familiar. And one advantage
is that there is some sort of the spec that regulates the logic. I do find your
logic quite intuitive and for C++ at least we can definitely make bigger
changes. However, it also helps to build on top of something that already
works. I am only worried about covering the corner cases enough.

May be prototyping the solution you are suggesting is a good way forward.

It also means that explicit conversion would always be permitted if
implicit conversion is permitted in one of the directions. In other
words, targets wouldn't be able to configure explicit conversion for
their address spaces; the behavior would always be implied. This makes
it hard to keep the current C/C++ address space model (no implicit
conversions, all explicit conversions) in a more generic design. That
model only works currently because some code is locked behind the OpenCL
language option.

But this logic comes from C/C++, generally speaking I can't think of the case
where implicit conversion would be allowed between the types that won't be
explicitly convertible. I don't think there is anything OpenCL specific about this.
I think address space conversion logic is pretty much extending qualifiers in
C/C++. Do you think there is any use case that needs different logic? Then
perhaps we will have more work to do because qualifiers might not be the right
fit for the address spaces with such logic.

Yes, that's why I am wondering if generic address space should be
introduced in C++ as purely logical address space concept?

If this would be a logical concept only, would it be implemented purely
on the Clang level and not via LLVM addrspaces? I'm not sure how the
realization of that address space would work out. For targets without
such an address space, would every use of a pointer to the generic
address space require a check to determine which 'subspace' the pointer
belongs to? Would targets have to enumerate their address spaces to
support this?

Generic AS would have to be preserved in the LLVM IR too and then it can
be lowered by the backed. The best solution would be to add some special HW
support to allow translating address spaces efficiently. However it can be
emulated by the compiler but then a toolchain should provide some way to
detect in what specific AS an object in generic AS points to. This can be done
by adding special intrinsics. However this checks are quite costly. There is some
help for reducing the overheads of generic address space provided in LLVM already
in form of a special pass that allows to infer the specific address space where
applicable.

That is a viable idea, but I think that if we would go through the
trouble of including an implicit header containing
implementation-specific configuration, then we might as well encode it
into the target definitions and save on the header in the first place.

I think it makes more sense for a language extension like OpenCL as it
needs to encode those regardless of target, so having a reusable set of
definitions is good for that use case. But for a specific target, it's
less useful.

Yes, I think what I am suggesting is somehow orthogonal to the target
address space idea by you. I am sure adding a way to specify target AS
rules is definitely useful and TableGen seems like a good approach.

To help developing the language dialects we could also come up with some sort of
compiler directives to describe address space rules. We can use this syntax to
simplify and unify implementation of dialects. As soon as address space logic fits
within those standard rules no modification to address spaces would be needed at all.

Cheers,
Anastasia

Yes, I believe we might not have any upstream example for this and
hence current implementation is not complete. However, the current
approach has some sort of the guidelines on how the ambiguity can
be resolved that we would need to figure out with the new logic.

I guess we could use the implicit/explicit conversion for this, however
it might become more convoluted.

Well, if we do change the interface to model conversion legality instead of superspaces, the functional behavior can be the same as today. We would just call the proper method for checking implicit AS conversion as we call isAddressSpaceSupersetOf today. However, if we decide to change the overload behavior to consider shorter AS conversion paths over longer ones, the intuition might not be as obvious as it would be with the superspace approach.

I think there might be a little bit of that. For the developers from embedded
C or OpenCL background this concept is quite familiar. And one advantage
is that there is some sort of the spec that regulates the logic. I do find your
logic quite intuitive and for C++ at least we can definitely make bigger
changes. However, it also helps to build on top of something that already
works. I am only worried about covering the corner cases enough.

May be prototyping the solution you are suggesting is a good way forward.

Yes, prototyping it is probably a good idea, at least to get an idea of how it could look in practice.

But this logic comes from C/C++, generally speaking I can’t think of the case
where implicit conversion would be allowed between the types that won’t be
explicitly convertible. I don’t think there is anything OpenCL specific about this.
I think address space conversion logic is pretty much extending qualifiers in
C/C++. Do you think there is any use case that needs different logic? Then
perhaps we will have more work to do because qualifiers might not be the right
fit for the address spaces with such logic.

There’s nothing wrong with the type representation, it’s simply that the C/C++ behavior cannot actually be captured with the superspace model. In the current C/C++ model, the default behavior for address space conversions is that converting between any two address spaces implicitly is always prohibited, but converting explicitly is always permitted. This cannot be expressed in the superspace model, as there is no way to make explicit conversion between AS1 and AS2 legal without also either making AS1->AS2 or AS2->AS1 implicitly legal, as ‘isAddressSpaceOverlapping’ is dependent on ‘isAddressSpaceSuperSpaceOf’. This is the reason why the conversion logic in checkAddressSpaceCast is hidden behind the OpenCL option, for example.

Generic AS would have to be preserved in the LLVM IR too and then it can
be lowered by the backed. The best solution would be to add some special HW
support to allow translating address spaces efficiently. However it can be
emulated by the compiler but then a toolchain should provide some way to
detect in what specific AS an object in generic AS points to. This can be done
by adding special intrinsics. However this checks are quite costly. There is some
help for reducing the overheads of generic address space provided in LLVM already
in form of a special pass that allows to infer the specific address space where
applicable.

If it requires hardware or backend support it doesn’t really feel like a logical concept any more. The overhead of the runtime checking also sounds a bit scary. I think that if a target or language supports a generic address space, then they can use that for methods if they want to save on the typing overhead. Otherwise I think they’ll just have to wait until there is a way to automatically deduce address space qualified member functions at compile time.

There’s nothing wrong with the type representation, it’s simply that the C/C++ behavior cannot actually be captured with the superspace model. > In the current C/C++ model, the default behavior for address space conversions is that converting between any two address spaces implicitly is > always prohibited, but converting explicitly is always permitted. This cannot be expressed in the superspace model, as there is no way to make > explicit conversion between AS1 and AS2 legal without also either making AS1->AS2 or AS2->AS1 implicitly legal, as ‘isAddressSpaceOverlapping’ is dependent on ‘isAddressSpaceSuperSpaceOf’.

I thought that this is because the address space support is incomplete. At least according to the embedded C report the casts to non-overlapping address spaces should be disallowed. It doesn’t clarify however whether it applies to both implicit and explicit cast.

If it requires hardware or backend support it doesn’t really feel like a logical concept any more. The overhead of the runtime checking also sounds a bit scary. I think that if a target or language supports a generic address space, then they can use that for methods if they want to save on the typing overhead. Otherwise I think they’ll just have to wait until there is a way to automatically deduce address space qualified member functions at compile time.

I think that the deduction won’t help here because we would like the member functions to be called on objects in different address spaces, at least for the general case. And then we end up with truly generic cases where multiple address spaces are to be used with the method on the place of generic. There are multiple ways to solve this problem including duplicating the methods but they don’t seem to be ideal. I feel that the address space support for C++ without something like generic address space can’t be complete. I will start a separate RFC summarizing my thoughts with a few examples perhaps to avoid diverging the focus of this RFC.

Thanks!

Anastasia

There's nothing wrong with the type representation, it's simply that the C/C++ behavior cannot actually be captured with the superspace model. > In the current C/C++ model, the default behavior for address space conversions is that converting between any two address spaces implicitly is > always prohibited, but converting explicitly is always permitted. This cannot be expressed in the superspace model, as there is no way to make > explicit conversion between AS1 and AS2 legal without also either making AS1->AS2 or AS2->AS1 implicitly legal, as 'isAddressSpaceOverlapping' is dependent on 'isAddressSpaceSuperSpaceOf'.

I thought that this is because the address space support is incomplete. At least according to the embedded C report the casts to non-overlapping address spaces should be disallowed. It doesn't clarify however whether it applies to both implicit and explicit cast.

I believe that 'cast' in standardese means 'explicit cast'. I don't think implicit casts are ever referred to as casts, at least in the C standards. The Embedded-C report does make it clear separately that "If a pointer into address space A is assigned to a pointer into a different address space B, a constraint requires that A be a subset of B.", which enshrines the implicit conversion rule.

In order to explicitly cast from an AS A to an AS B without invoking UB, one of the ASes must be a subset of the other (overlapping). Technically, E-C doesn't prohibit explicit casts where the ASes are disjoint (which matches the current 'generic' implementation in Clang), but I'm of the opinion that this is a mistake. There is no reason to allow such casts as they *always* invoke UB.

So, I guess the current C/C++ behavior is according to spec if you think of all of the target ASes as disjoint (cannot implicitly convert, but all explicit conversion is allowed), but if we follow those rules then there is no way for a target to prevent explicit casting between disjoint address spaces, which is pretty bad.

If it requires hardware or backend support it doesn't really feel like a logical concept any more. The overhead of the runtime checking also sounds a bit scary. I think that if a target or language supports a generic address space, then they can use that for methods if they want to save on the typing overhead. Otherwise I think they'll just have to wait until there is a way to automatically deduce address space qualified member functions at compile time.

I think that the deduction won't help here because we would like the member functions to be called on objects in different address spaces, at least for the general case. And then we end up with truly generic cases where multiple address spaces are to be used with the method on the place of generic. There are multiple ways to solve this problem including duplicating the methods but they don't seem to be ideal. I feel that the address space support for C++ without something like generic address space can't be complete. I will start a separate RFC summarizing my thoughts with a few examples perhaps to avoid diverging the focus of this RFC.

Well, if the deduction lets us expand a template method with a given address space, then it becomes general. It would cause code duplication, but I think it's the only way to get any kind of decent performance out of it. I'm not sure how having to do a target-specific address space check at runtime every time we want to access 'this' will work out.

Thanks!

Anastasia

So, I guess the current C/C++ behavior is according to spec if you think of all of the target ASes as disjoint (cannot implicitly convert, but all explicit conversion is allowed), but if we follow those rules then there is no way for a target to prevent explicit casting between disjoint address spaces, which is pretty bad.

I would agree. We should probably make this more usable.

Well, if the deduction lets us expand a template method with a given address space, then it becomes general. It would cause code duplication, but I think it’s the only way to get any kind of decent performance out of it. I’m not sure how having to do a target-specific address space check at runtime every time we want to access ‘this’ will work out.

Yes, that’s why I think evolution of HW to support the physical address translation is the only way forward if we don’t want to regress anything due to the use of address spaces. But in the mean time we could workaround with code duplication. However, it is also not available option now. Generic address space would offer a complete solution at least in a short term and it can open paths to multiple better implementations in the future. We already have some address space inference functionality available that can eliminate some of the horrible run-time overheads.

Anastasia

Just FYI I feel this work could unblock the following issue: https://bugs.llvm.org/show_bug.cgi?id=41730

So, I guess the current C/C++ behavior is according to spec if you think of all of the target ASes as disjoint (cannot implicitly convert, but all explicit conversion is allowed), but if we follow those rules then there is no way for a target to prevent explicit casting between disjoint address spaces, which is pretty bad.

I would agree. We should probably make this more usable.

Well, if the deduction lets us expand a template method with a given address space, then it becomes general. It would cause code duplication, but I think it's the only way to get any kind of decent performance out of it. I'm not sure how having to do a target-specific address space check at runtime every time we want to access 'this' will work out.

Yes, that's why I think evolution of HW to support the physical address translation is the only way forward if we don't want to regress anything due to the use of address spaces. But in the mean time we could workaround with code duplication. However, it is also not available option now. Generic address space would offer a complete solution at least in a short term and it can open paths to multiple better implementations in the future. We already have some address space inference functionality available that can eliminate some of the horrible run-time overheads.

It's a noble goal, but I think evolving the hardware isn't necessarily an option for everyone...

In any case, should I submit a patch with the changes mentioned in the RFC and we can move forward from there? I guess we don't necessarily agree on how the validity of conversions should be expressed, but I don't see how to make the solution fully configurable by both targets and languages while keeping the overlap model.

/ Bevin

In any case, should I submit a patch with the changes mentioned in the RFC and we can move forward from there?

Cool! Looking forward to it!

I guess we don’t necessarily agree on how the validity of conversions should be expressed, but I don’t see how to make the solution fully configurable by both targets and languages while keeping the overlap model

I think we can focus on solving this for the targets then first. Because we already have some solution for language dialects. Once we have both perhaps we can try to see if it makes sense to generalize!

Thanks!
Anastasia