[cfe-dev] RFC: Adding availability attributes to the libc++ headers
Duncan P. N. Exon Smith via cfe-dev
cfe-dev at lists.llvm.org
Mon Feb 8 22:12:11 PST 2016
Manman and I are working on adding availability attributes to the libc++
headers to express to `clang` which dylib feautures are available on
Apple platforms (and at what versions).
I'd love some feedback! Please read on.
What are availability attributes?
=================================
Availability attributes describe when API is available. It's widely
used on Apple platforms; I'm not sure how much use it has outside of
that. For example:
int foo()
__attribute__((availability(macosx,introduced=10.9,
deprecated=10.10,obsoleted=10.11)))
__attribute__((availability(ios,introduced=8.0)))
__attribute__((availability(android,unavailable)));
This says that `foo()` was added to the dylib in Mac OS X 10.9,
deprecated in 10.10, and obsoleted in 10.11; added to iOS in 8.0; and
isn't available at all on Android.
Availability attributes can be attached to most (all?) declarations,
including types, variables, and functions.
Since libc++ has a stable API, I only expect to use "introduced" and
"unavailable" (and we're only working on macosx, ios, tvos and watchos).
What are the semantics when API isn't available?
================================================
There are two cases here. The simple case is when API is straight-up
"unavailable": the frontend gives a diagnostic when it is used by a
function which is *not* marked unavailable. For example:
int __foo() __attribute__((unavailable));
inline int foo() __attribute__((unavailable)) {
return __foo(); // no diagnostic
}
int bar() { return foo(); } // error: foo() unavailable
int baz() { return __foo(); } // error: __foo() unavailable
What about deploying to old versions?
-------------------------------------
The seecond case is when a deployment target has been set on the
command-line, and it's *older* than when an API has been introduced.
I'll start with an example:
$ cat t.cpp
int foo() __attribute__((availability(macosx,introduced=10.10)));
int bar() { return foo(); }
$ clang t.cpp -mmacosx-version-min=10.9
The default semantics are: weak-link the symbol for foo(). The user is
expected to check at runtime to see if it's available. Continuing the
example:
return foo ? foo() : my_workaround_when_foo_is_missing();
This is not a good usage model for libc++ API. We'd rather give a hard
diagnostic in this case. This requires a new attribute flag, "noweak":
$ cat t.cpp
int foo() __attribute__((availability(macosx,noweak,introduced=10.10)));
int bar() { return foo(); } // error: 'foo' not available until OS X 10.10
$ clang t.cpp -mmacosx-version-min=10.9
Instead of weak-linking foo(), diagnose all uses of it.
What would this look like in the libc++ headers?
================================================
This only affects header code that depends on the dylib. Most of libc++
is header-only, and doesn't need any markup at all.
I've attached WIP-libcxx-availability.patch, which handles all of
<__locale>, and the not-yet-supported parts of <exception>,
<experimental/optional>, <new>, and <shared_mutex>. It *does not* use
`noweak`, but adding -Werror=partial-availability will have a similar
effect.
- The code in __config is too strict. As currently written, it would
prevent libc++ developers from testing new library code. It needs a
macro to control whether to apply the availability markup. I
haven't thought much about this; please let me know if there's an
obvious solution.
- There's currently a relevant bug in clang. If an unavailable
templated function has a non-template-dependent parameter that is
also unavailable, clang incorrectly emits a diagnostic. I sent a
[patch](http://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20160208/149470.html)
to cfe-commits.
- I'd welcome feedback on how I structed the macros. In the WIP
patch I named macros based on functional groups, but there are other
options.
Rejected alternative: delay diagnostics until first ODR-use of caller
=====================================================================
We looked at the possibility of delaying availability diagnostics until
the caller's first ODR-use if the caller was a `inline`, `template`d, or
`static` (anything that is only emitted when it's ODR-used).
Effectively, this would let us decorate *only* the functions on the
dylib boundary, and then clang would auto-magically propagate the
attributes up through the headers.
However, cases like this seemed prohibitively expensive to diagnose:
int foo();
int bar() { return foo(); } // error: 'foo' unavailable
int __foo() __attribute__((unavailable)); // note: '__foo' explicitly unavailable
inline int foo() { return __foo(); } // note: 'foo' implicitly unavailable
Our goal was: diagnose that bar() cannot call foo() because it's
unavailable; foo() is unavailable because it calls __foo(). The order
of definitions makes this pretty awkward though. In general, I think it
would require keeping track of all ODR-users of every function call, so
that on parsing the body of foo() we could diagnose its call in bar().
Explicitly marking foo() unavailable keeps the frontend simple, and it
also gives a better experience to library users. They don't care about
the full ODR-use chain that leads to the dylib; they just need to know
that the library feature isn't available.
More information about the cfe-dev
mailing list