Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/AST/PlatformKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ AVAILABILITY_PLATFORM(macCatalystApplicationExtension, "application extensions f
AVAILABILITY_PLATFORM(FreeBSD, "FreeBSD")
AVAILABILITY_PLATFORM(OpenBSD, "OpenBSD")
AVAILABILITY_PLATFORM(Windows, "Windows")
AVAILABILITY_PLATFORM(Android, "Android")

#undef AVAILABILITY_PLATFORM
10 changes: 10 additions & 0 deletions include/swift/Runtime/RuntimeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,16 @@ FUNCTION(TSanInoutAccess, tsan, __tsan_external_write, C_CC, AlwaysAvailable,
EFFECT(RuntimeEffect::NoEffect),
UNKNOWN_MEMEFFECTS)

// int32 __isOSVersionAtLeast(uint32_t major, uint32_t minor, uint32_t patch);
// This a C builtin provided by compiler-rt.
FUNCTION(OSVersionAtLeast, libgcc, __isOSVersionAtLeast,
C_CC, AlwaysAvailable,
RETURNS(Int32Ty),
ARGS(Int32Ty, Int32Ty, Int32Ty),
ATTRS(NoUnwind),
EFFECT(RuntimeEffect::NoEffect),
UNKNOWN_MEMEFFECTS)

// int32 __isPlatformVersionAtLeast(uint32_t platform, uint32_t major,
// uint32_t minor, uint32_t patch);
// This a C builtin provided by compiler-rt.
Expand Down
10 changes: 10 additions & 0 deletions lib/AST/PlatformKindUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ swift::basePlatformForExtensionPlatform(PlatformKind Platform) {
case PlatformKind::FreeBSD:
case PlatformKind::OpenBSD:
case PlatformKind::Windows:
case PlatformKind::Android:
case PlatformKind::none:
return std::nullopt;
}
Expand Down Expand Up @@ -164,6 +165,8 @@ static bool isPlatformActiveForTarget(PlatformKind Platform,
return Target.isOSFreeBSD();
case PlatformKind::Windows:
return Target.isOSWindows();
case PlatformKind::Android:
return Target.isAndroid();
case PlatformKind::none:
llvm_unreachable("handled above");
}
Expand Down Expand Up @@ -219,6 +222,10 @@ static PlatformKind platformForTriple(const llvm::Triple &triple,
: PlatformKind::visionOS);
}

if (triple.isAndroid()) {
return PlatformKind::Android;
}

return PlatformKind::none;
}

Expand Down Expand Up @@ -291,6 +298,8 @@ swift::tripleOSTypeForPlatform(PlatformKind platform) {
return llvm::Triple::OpenBSD;
case PlatformKind::Windows:
return llvm::Triple::Win32;
case PlatformKind::Android:
return llvm::Triple::Linux;
case PlatformKind::none:
return std::nullopt;
}
Expand Down Expand Up @@ -326,6 +335,7 @@ bool swift::isPlatformSPI(PlatformKind Platform) {
case PlatformKind::OpenBSD:
case PlatformKind::FreeBSD:
case PlatformKind::Windows:
case PlatformKind::Android:
case PlatformKind::none:
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/Basic/Platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ llvm::VersionTuple swift::getVersionForTriple(const llvm::Triple &triple) {
return triple.getOSVersion();
} else if (triple.isOSWindows()) {
return triple.getOSVersion();
} else if (triple.isAndroid()) {
return triple.getEnvironmentVersion();
}
return llvm::VersionTuple(/*Major=*/0, /*Minor=*/0, /*Subminor=*/0);
}
Expand Down
17 changes: 17 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,12 @@ void importer::getNormalInvocationArguments(
});
}

if (triple.isAndroid()) {
invocationArgStrs.insert(invocationArgStrs.end(), {
"-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that the way this works is to turn off all compile-time preprocessor checking of Android API versions, turning them into link-time failures instead, if the API is more recent than the target API and the appropriate shared library link-time checking is enabled for strong references.

I am leery of making this change to all Swift code compiled for Android: is there some way we can only limit this to files/modules that need it because they contain #available checks?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean that this turns off processor checking of Android API versions? This only changes the behaviour of the availability attribute attached to specific declarations.

#if defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__)
#define __BIONIC_AVAILABILITY(__what, ...) __attribute__((__availability__(android,__what __VA_OPT__(,) __VA_ARGS__)))
#define __INTRODUCED_IN_NO_GUARD_FOR_NDK(api_level) __INTRODUCED_IN(api_level)
#else
#define __BIONIC_AVAILABILITY(__what, ...) __attribute__((__availability__(android,strict,__what __VA_OPT__(,) __VA_ARGS__)))
#define __INTRODUCED_IN_NO_GUARD_FOR_NDK(api_level)
#endif

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe my perspective is skewed from only being familiar with Apple's SDKs, but I don't understand why we would not want this to be the default. From my quick reading it looks like it works exactly the same as on Apple platforms. The compiler will enforce that you must always check availability before calling the APIs which may not be available, so unless an API is mis-annotated there shouldn't be any harm and it seems like a strictly better developer experience to have access to newer APIs as long as you verify that they're present before you use them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean that this turns off processor checking of Android API versions?

In order to now be able to use older APIs than the target API, you have to disable compile-time API version checking to some extent. How extensive that would be is what worried me. Mads and I have been discussing this in Slack, which you saw now, and it looks like most Bionic APIs since NDK 28 can continue to be version checked at build time to some extent, by using the version annotations.

If that's the case, we should explore it and see what it can check.

The compiler will enforce that you must always check availability before calling the APIs which may not be available, so unless an API is mis-annotated there shouldn't be any harm and it seems like a strictly better developer experience to have access to newer APIs as long as you verify that they're present before you use them.

I have never used #available, because I have never built Swift for Darwin platforms, nor have I ever used these new runtime availability options on Android, which were just added a couple years ago with NDK 26. I've been asking Mads privately for more info on how #available works with C APIs, and he says he can maintain some build-time API version checking.

Let us see what he comes up with.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After giving it some more though, I agree that enabling weak symbols is the behaviour we want. The thing that they changed in r28, was actually removing __ANDROID__API checks from libc. This means that if you have enabled weak symbols, you can actually use the availability checking to call methods introduced in APIs greater than your minimum SDK version. Whereas before, it was just not possible, as the symbols were removed.

It seems like the coverage of the Bionic in terms of INTRODUCED_IN is quite extensive, and has been since r18.

I also added support to ClangImporter, such that it will actually import the NDK headers with correct @availability attributes.

This mode should not break anything for existing users that are compiling for Android. If their code was compiling, then all the NDK methods they were using must be supported on the version that they are compiling for, because of the __ANDROID__API check in r27. And if they now try to use later APIs, then will be statically told to add availability checking.

Since this PR just adds support to #available/@available, it gives us the static availability checking that I think @finagolfin is referring to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing that they changed in r28, was actually removing __ANDROID__API checks from libc.

About 80% were replaced when going from NDK 27 to 28, not all.

I also added support to ClangImporter, such that it will actually import the NDK headers with correct @Availability attributes.

Did you try this yourself? If so, add some tests for it.

This mode should not break anything for existing users that are compiling for Android.

Nobody has suggested it would. Rather the question was whether introducing new Swift code would then keep the same level of compile-time version checking as before, after you set this macro, which is why this is not the default in the NDK.

And if they now try to use later APIs, then will be statically told to add availability checking.

Great, please add some tests for that and you will have not only kept this checking the same, but added an important new static version checking feature of multiple API levels. 😺

Thanks for working on and looking into all this, @madsodgaard, I think all Android devs would love to have this. I will try this out locally in the coming days and review it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to work now, but I've asked for more test cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added tests that use Bionic APIs

});
}

if (triple.isOSWindows()) {
switch (triple.getArch()) {
default: llvm_unreachable("unsupported Windows architecture");
Expand Down Expand Up @@ -2568,6 +2574,10 @@ PlatformAvailability::PlatformAvailability(const LangOptions &langOpts)
deprecatedAsUnavailableMessage = "";
break;

case PlatformKind::Android:
deprecatedAsUnavailableMessage = "";
break;

case PlatformKind::none:
break;
}
Expand Down Expand Up @@ -2616,6 +2626,9 @@ bool PlatformAvailability::isPlatformRelevant(StringRef name) const {
case PlatformKind::Windows:
return name == "windows";

case PlatformKind::Android:
return name == "android";

case PlatformKind::none:
return false;
}
Expand Down Expand Up @@ -2692,6 +2705,10 @@ bool PlatformAvailability::treatDeprecatedAsUnavailable(
case PlatformKind::Windows:
// No deprecation filter on Windows
return false;

case PlatformKind::Android:
// The minimum Android API level supported by Swift is 21
return major <= 20;
}

llvm_unreachable("Unexpected platform");
Expand Down
1 change: 1 addition & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9855,6 +9855,7 @@ void ClangImporter::Implementation::importAttributes(
PlatformKind::watchOSApplicationExtension)
.Case("xros_app_extension",
PlatformKind::visionOSApplicationExtension)
.Case("android", PlatformKind::Android)
.Default(std::nullopt);
if (!platformK)
continue;
Expand Down
15 changes: 11 additions & 4 deletions lib/IRGen/IRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,18 @@ llvm::Value *
IRGenFunction::emitTargetOSVersionAtLeastCall(llvm::Value *major,
llvm::Value *minor,
llvm::Value *patch) {
auto fn = IGM.getPlatformVersionAtLeastFunctionPointer();
// compiler-rt in the NDK does not include __isPlatformVersionAtLeast
// but only __isOSVersionAtLeast
if (IGM.Triple.isAndroid()) {
auto fn = IGM.getOSVersionAtLeastFunctionPointer();
return Builder.CreateCall(fn, {major, minor, patch});
} else {
auto fn = IGM.getPlatformVersionAtLeastFunctionPointer();

llvm::Value *platformID =
llvm::ConstantInt::get(IGM.Int32Ty, getBaseMachOPlatformID(IGM.Triple));
return Builder.CreateCall(fn, {platformID, major, minor, patch});
llvm::Value *platformID =
llvm::ConstantInt::get(IGM.Int32Ty, getBaseMachOPlatformID(IGM.Triple));
return Builder.CreateCall(fn, {platformID, major, minor, patch});
}
}

llvm::Value *
Expand Down
2 changes: 2 additions & 0 deletions lib/IRGen/TBDGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ getLinkerPlatformId(OriginallyDefinedInAttr::ActiveVersion Ver,
llvm_unreachable("not used for this platform");
case swift::PlatformKind::Windows:
llvm_unreachable("not used for this platform");
case swift::PlatformKind::Android:
llvm_unreachable("not used for this platform");
case swift::PlatformKind::iOS:
case swift::PlatformKind::iOSApplicationExtension:
if (target && target->isMacCatalystEnvironment())
Expand Down
3 changes: 3 additions & 0 deletions lib/PrintAsClang/DeclAndTypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1830,6 +1830,9 @@ class DeclAndTypePrinter::Implementation
case PlatformKind::Windows:
plat = "windows";
break;
case PlatformKind::Android:
plat = "android";
break;
case PlatformKind::none:
llvm_unreachable("handled above");
}
Expand Down
2 changes: 2 additions & 0 deletions lib/SymbolGraphGen/AvailabilityMixin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ StringRef getDomain(const SemanticAvailableAttr &AvAttr) {
return { "OpenBSD" };
case swift::PlatformKind::Windows:
return { "Windows" };
case swift::PlatformKind::Android:
return { "Android" };
case swift::PlatformKind::none:
return { "*" };
}
Expand Down
8 changes: 8 additions & 0 deletions stdlib/cmake/modules/AddSwiftStdlib.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,9 @@ function(add_swift_target_library name)
elseif(sdk STREQUAL "LINUX")
list(APPEND swiftlib_module_depends_flattened
${SWIFTLIB_SWIFT_MODULE_DEPENDS_LINUX})
elseif(sdk STREQUAL "ANDROID")
list(APPEND swiftlib_module_depends_flattened
${SWIFTLIB_SWIFT_MODULE_DEPENDS_ANDROID})
elseif(sdk STREQUAL "LINUX_STATIC")
list(APPEND swiftlib_module_depends_flattened
${SWIFTLIB_SWIFT_MODULE_DEPENDS_LINUX_STATIC})
Expand Down Expand Up @@ -2541,6 +2544,11 @@ function(add_swift_target_library name)
list(APPEND swiftlib_link_flags_all "-Wl,-z,max-page-size=16384")
endif()

# This is a Android-specific hack till we transition the stdlib fully to versioned triples.
if(sdk STREQUAL "ANDROID" AND name STREQUAL "swiftSwiftReflectionTest")
list(APPEND swiftlib_swift_compile_flags_all "-target" "${SWIFT_SDK_ANDROID_ARCH_${arch}_TRIPLE}${SWIFT_ANDROID_API_LEVEL}")
endif()

if (SWIFTLIB_BACK_DEPLOYMENT_LIBRARY)
set(back_deployment_library_option BACK_DEPLOYMENT_LIBRARY ${SWIFTLIB_BACK_DEPLOYMENT_LIBRARY})
else()
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public func _stdlib_isOSVersionAtLeast_AEIC(
_ minor: Builtin.Word,
_ patch: Builtin.Word
) -> Builtin.Int1 {
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)) && SWIFT_RUNTIME_OS_VERSIONING
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS) || os(Android)) && SWIFT_RUNTIME_OS_VERSIONING
if Int(major) == 9999 {
return true._value
}
Expand Down
5 changes: 5 additions & 0 deletions test/ClangImporter/Inputs/custom-modules/AndroidVersioning.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include <android/versioning.h>

void FunctionIntroducedIn24() __INTRODUCED_IN(24);
void FunctionIntroducedIn28() __INTRODUCED_IN(28);

5 changes: 5 additions & 0 deletions test/ClangImporter/Inputs/custom-modules/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ module script {
header "script.h"
}

module AndroidVersioning {
header "AndroidVersioning.h"
export *
}

module AvailabilityExtras {
header "AvailabilityExtras.h"
export *
Expand Down
4 changes: 4 additions & 0 deletions test/ClangImporter/android-sdk-macros.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// RUN: %swift -target aarch64-unknown-linux-android28 -typecheck %s -parse-stdlib -dump-clang-diagnostics 2>&1 | %FileCheck %s -check-prefix CHECK-WEAK-SYMBOLS

// CHECK-WEAK-SYMBOLS: -D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__

26 changes: 26 additions & 0 deletions test/ClangImporter/availability_android.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %target-swift-frontend -typecheck -verify -I %S/Inputs/custom-modules -verify-ignore-unknown -target aarch64-unknown-linux-android24 %s

// REQUIRES: OS=linux-android || OS=linux-androideabi

import AndroidVersioning
import Android

FunctionIntroducedIn24()

FunctionIntroducedIn28()
// expected-error@-1 {{'FunctionIntroducedIn28()' is only available in Android 28 or newer}}
// expected-note@-2 {{add 'if #available' version check}}

func test_ifaddrs_introduced_in_24() {
var ifaddr_ptr: UnsafeMutablePointer<ifaddrs>? = nil
if getifaddrs(&ifaddr_ptr) == 0 {
freeifaddrs(ifaddr_ptr)
}
}

func test_getentropy_introduced_in_28() {
var buffer: [UInt8] = .init(repeating: 0, count: 16)
getentropy(&buffer, buffer.count)
// expected-error@-1 {{'getentropy' is only available in Android 28 or newer}}
// expected-note@-2 {{add 'if #available' version check}}
}
12 changes: 12 additions & 0 deletions test/IRGen/availability_android.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %target-swift-frontend -target aarch64-unknown-linux-android24 -primary-file %s -emit-ir | %FileCheck %s

// CHECK-LABEL: define{{.*}}$s20availability_android0A5CheckyyF
// CHECK: call swiftcc i1 @"$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF"(

// REQUIRES: OS=linux-android || OS=linux-androideabi

public func availabilityCheck() {
if #available(Android 28, *) {
print("test")
}
}
59 changes: 59 additions & 0 deletions test/attr/attr_availability_android.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// RUN: %swift -typecheck -verify -parse-stdlib -target aarch64-unknown-linux-android28 %s

@available(Android, introduced: 1.0, deprecated: 2.0, obsoleted: 28.0,
message: "you don't want to do that anyway")
func doSomething() { }
// expected-note @-1{{'doSomething()' was obsoleted in Android 28.0}}

doSomething() // expected-error{{'doSomething()' is unavailable in Android: you don't want to do that anyway}}

// Preservation of major.minor.micro
@available(Android, introduced: 1.0, deprecated: 2.0, obsoleted: 27.0)
func doSomethingElse() { }
// expected-note @-1{{'doSomethingElse()' was obsoleted in Android 27.0}}

doSomethingElse() // expected-error{{'doSomethingElse()' is unavailable in Android}}

// Test deprecations in 28.0 and later

@available(Android, introduced: 1.1, deprecated: 28.0,
message: "Use another function")
func deprecatedFunctionWithMessage() { }

deprecatedFunctionWithMessage() // expected-warning{{'deprecatedFunctionWithMessage()' was deprecated in Android 28.0: Use another function}}


@available(Android, introduced: 1.0, deprecated: 28.0)
func deprecatedFunctionWithoutMessage() { }

deprecatedFunctionWithoutMessage() // expected-warning{{'deprecatedFunctionWithoutMessage()' was deprecated in Android 28.0}}

@available(Android, introduced: 1.0, deprecated: 28.0,
message: "Use BetterClass instead")
class DeprecatedClass { }

func functionWithDeprecatedParameter(p: DeprecatedClass) { } // expected-warning{{'DeprecatedClass' was deprecated in Android 28.0: Use BetterClass instead}}

@available(tvOS, introduced: 7.0, deprecated: 29,
message: "Use BetterClass instead")
class DeprecatedClassIn29_0 { }

// Elements deprecated later than the minimum deployment target (which is 28.0, in this case) should not generate warnings
func functionWithDeprecatedLaterParameter(p: DeprecatedClassIn29_0) { }

@available(Android, introduced: 30)
func functionIntroducedOnAndroid30() { }

if #available(iOS 17.3, *) {
functionIntroducedOnAndroid30() // expected-error{{'functionIntroducedOnAndroid30()' is only available in Android 30 or newer}}
// expected-note @-1{{add 'if #available' version check}}{{3-34=if #available(Android 30, *) {\n functionIntroducedOnAndroid30()\n \} else {\n // Fallback on earlier versions\n \}}}
}

if #available(Android 28, *) {
functionIntroducedOnAndroid30() // expected-error{{'functionIntroducedOnAndroid30()' is only available in Android 30 or newer}}
// expected-note @-1{{add 'if #available' version check}}{{3-34=if #available(Android 30, *) {\n functionIntroducedOnAndroid30()\n \} else {\n // Fallback on earlier versions\n \}}}
}

if #available(Android 30, *) {
functionIntroducedOnAndroid30()
}
4 changes: 4 additions & 0 deletions tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ static void reportAvailabilityAttributes(ASTContext &Ctx, const Decl *D,
static UIdent PlatformFreeBSD("source.availability.platform.freebsd");
static UIdent PlatformOpenBSD("source.availability.platform.openbsd");
static UIdent PlatformWindows("source.availability.platform.windows");
static UIdent PlatformAndroid("source.availability.platform.android");
std::vector<SemanticAvailableAttr> Scratch;

for (auto Attr : getAvailableAttrs(D, Scratch)) {
Expand Down Expand Up @@ -743,6 +744,9 @@ static void reportAvailabilityAttributes(ASTContext &Ctx, const Decl *D,
case PlatformKind::Windows:
PlatformUID = PlatformWindows;
break;
case PlatformKind::Android:
PlatformUID = PlatformAndroid;
break;
}
// FIXME: [availability] Handle other availability domains?

Expand Down