|
1 |
| -//===--- SILGenBackDeploy.cpp - SILGen for back deployment ----------------===// |
| 1 | +//===--- SILGenAvailability.cpp - SILGen for availability queries ---------===// |
2 | 2 | //
|
3 | 3 | // This source file is part of the Swift.org open source project
|
4 | 4 | //
|
5 |
| -// Copyright (c) 2022 Apple Inc. and the Swift project authors |
| 5 | +// Copyright (c) 2022 - 2025 Apple Inc. and the Swift project authors |
6 | 6 | // Licensed under Apache License v2.0 with Runtime Library Exception
|
7 | 7 | //
|
8 | 8 | // See https://swift.org/LICENSE.txt for license information
|
|
13 | 13 | #include "SILGenFunction.h"
|
14 | 14 | #include "SILGenFunctionBuilder.h"
|
15 | 15 | #include "Scope.h"
|
16 |
| -#include "swift/Basic/Platform.h" |
17 | 16 | #include "swift/Basic/Assertions.h"
|
| 17 | +#include "swift/Basic/Platform.h" |
18 | 18 | #include "swift/SIL/SILDeclRef.h"
|
19 | 19 |
|
20 | 20 | using namespace swift;
|
21 | 21 | using namespace Lowering;
|
22 | 22 |
|
| 23 | +/// Emit literals for the major, minor, and subminor components of the version |
| 24 | +/// and return a tuple of SILValues for them. |
| 25 | +static std::tuple<SILValue, SILValue, SILValue> |
| 26 | +emitVersionLiterals(SILLocation loc, SILGenBuilder &B, ASTContext &ctx, |
| 27 | + llvm::VersionTuple Vers) { |
| 28 | + unsigned major = Vers.getMajor(); |
| 29 | + unsigned minor = Vers.getMinor().value_or(0); |
| 30 | + unsigned subminor = Vers.getSubminor().value_or(0); |
| 31 | + |
| 32 | + SILType wordType = SILType::getBuiltinWordType(ctx); |
| 33 | + |
| 34 | + SILValue majorValue = B.createIntegerLiteral(loc, wordType, major); |
| 35 | + SILValue minorValue = B.createIntegerLiteral(loc, wordType, minor); |
| 36 | + SILValue subminorValue = B.createIntegerLiteral(loc, wordType, subminor); |
| 37 | + |
| 38 | + return std::make_tuple(majorValue, minorValue, subminorValue); |
| 39 | +} |
| 40 | + |
| 41 | +/// Emit a check that returns 1 if the running OS version is in |
| 42 | +/// the specified version range and 0 otherwise. The returned SILValue |
| 43 | +/// (which has type Builtin.Int1) represents the result of this check. |
| 44 | +static SILValue emitOSVersionRangeCheck(SILGenFunction &SGF, SILLocation loc, |
| 45 | + const VersionRange &range, |
| 46 | + bool forTargetVariant) { |
| 47 | + auto &ctx = SGF.getASTContext(); |
| 48 | + auto &B = SGF.B; |
| 49 | + |
| 50 | + // Emit constants for the checked version range. |
| 51 | + SILValue majorValue; |
| 52 | + SILValue minorValue; |
| 53 | + SILValue subminorValue; |
| 54 | + |
| 55 | + std::tie(majorValue, minorValue, subminorValue) = |
| 56 | + emitVersionLiterals(loc, B, ctx, range.getLowerEndpoint()); |
| 57 | + |
| 58 | + // Emit call to _stdlib_isOSVersionAtLeast(major, minor, patch) |
| 59 | + FuncDecl *versionQueryDecl = ctx.getIsOSVersionAtLeastDecl(); |
| 60 | + |
| 61 | + // When targeting macCatalyst, the version number will be an iOS version |
| 62 | + // number and so we call a variant of the query function that understands iOS |
| 63 | + // versions. |
| 64 | + if (forTargetVariant) |
| 65 | + versionQueryDecl = ctx.getIsVariantOSVersionAtLeastDecl(); |
| 66 | + |
| 67 | + assert(versionQueryDecl); |
| 68 | + |
| 69 | + auto declRef = SILDeclRef(versionQueryDecl); |
| 70 | + SILValue availabilityGTEFn = SGF.emitGlobalFunctionRef( |
| 71 | + loc, declRef, |
| 72 | + SGF.getConstantInfo(SGF.getTypeExpansionContext(), declRef)); |
| 73 | + |
| 74 | + SILValue args[] = {majorValue, minorValue, subminorValue}; |
| 75 | + return B.createApply(loc, availabilityGTEFn, SubstitutionMap(), args); |
| 76 | +} |
| 77 | + |
| 78 | +static SILValue |
| 79 | +emitOSVersionOrVariantVersionRangeCheck(SILGenFunction &SGF, SILLocation loc, |
| 80 | + const VersionRange &targetRange, |
| 81 | + const VersionRange &variantRange) { |
| 82 | + auto &ctx = SGF.getASTContext(); |
| 83 | + auto &B = SGF.B; |
| 84 | + |
| 85 | + SILValue targetMajorValue; |
| 86 | + SILValue targetMinorValue; |
| 87 | + SILValue targetSubminorValue; |
| 88 | + |
| 89 | + std::tie(targetMajorValue, targetMinorValue, targetSubminorValue) = |
| 90 | + emitVersionLiterals(loc, B, ctx, targetRange.getLowerEndpoint()); |
| 91 | + |
| 92 | + SILValue variantMajorValue; |
| 93 | + SILValue variantMinorValue; |
| 94 | + SILValue variantSubminorValue; |
| 95 | + |
| 96 | + std::tie(variantMajorValue, variantMinorValue, variantSubminorValue) = |
| 97 | + emitVersionLiterals(loc, B, ctx, variantRange.getLowerEndpoint()); |
| 98 | + |
| 99 | + FuncDecl *versionQueryDecl = |
| 100 | + ctx.getIsOSVersionAtLeastOrVariantVersionAtLeast(); |
| 101 | + |
| 102 | + assert(versionQueryDecl); |
| 103 | + |
| 104 | + auto declRef = SILDeclRef(versionQueryDecl); |
| 105 | + SILValue availabilityGTEFn = SGF.emitGlobalFunctionRef( |
| 106 | + loc, declRef, |
| 107 | + SGF.getConstantInfo(SGF.getTypeExpansionContext(), declRef)); |
| 108 | + |
| 109 | + SILValue args[] = {targetMajorValue, targetMinorValue, |
| 110 | + targetSubminorValue, variantMajorValue, |
| 111 | + variantMinorValue, variantSubminorValue}; |
| 112 | + return B.createApply(loc, availabilityGTEFn, SubstitutionMap(), args); |
| 113 | +} |
| 114 | + |
| 115 | +SILValue emitZipperedOSVersionRangeCheck(SILGenFunction &SGF, SILLocation loc, |
| 116 | + const VersionRange &targetRange, |
| 117 | + const VersionRange &variantRange) { |
| 118 | + auto &ctx = SGF.getASTContext(); |
| 119 | + auto &B = SGF.B; |
| 120 | + |
| 121 | + assert(ctx.LangOpts.TargetVariant); |
| 122 | + |
| 123 | + VersionRange targetVersion = targetRange; |
| 124 | + VersionRange variantVersion = variantRange; |
| 125 | + |
| 126 | + // We're building zippered, so we need to pass both macOS and iOS versions to |
| 127 | + // the runtime version range check. At run time that check will determine what |
| 128 | + // kind of process this code is loaded into. In a macOS process it will use |
| 129 | + // the macOS version; in an macCatalyst process it will use the iOS version. |
| 130 | + llvm::Triple targetTriple = ctx.LangOpts.Target; |
| 131 | + llvm::Triple variantTriple = *ctx.LangOpts.TargetVariant; |
| 132 | + |
| 133 | + // From perspective of the driver and most of the frontend, -target and |
| 134 | + // -target-variant are symmetric. That is, the user can pass either: |
| 135 | + // -target x86_64-apple-macosx10.15 \ |
| 136 | + // -target-variant x86_64-apple-ios13.1-macabi |
| 137 | + // or: |
| 138 | + // -target x86_64-apple-ios13.1-macabi \ |
| 139 | + // -target-variant x86_64-apple-macosx10.15 |
| 140 | + // |
| 141 | + // However, the runtime availability-checking entry points need to compare |
| 142 | + // against an actual running OS version and so can't be symmetric. Here we |
| 143 | + // standardize on "target" means macOS version and "targetVariant" means iOS |
| 144 | + // version. |
| 145 | + if (tripleIsMacCatalystEnvironment(targetTriple)) { |
| 146 | + assert(variantTriple.isMacOSX()); |
| 147 | + // Normalize so that "variant" always means iOS version. |
| 148 | + std::swap(targetVersion, variantVersion); |
| 149 | + std::swap(targetTriple, variantTriple); |
| 150 | + } |
| 151 | + |
| 152 | + // If there is no check for either the target platform or the target-variant |
| 153 | + // platform then the condition is trivially true. |
| 154 | + if (targetVersion.isAll() && variantVersion.isAll()) { |
| 155 | + SILType i1 = SILType::getBuiltinIntegerType(1, ctx); |
| 156 | + return B.createIntegerLiteral(loc, i1, true); |
| 157 | + } |
| 158 | + |
| 159 | + // If either version is "never" then the check is trivially false because it |
| 160 | + // can never succeed. |
| 161 | + if (targetVersion.isEmpty() || variantVersion.isEmpty()) { |
| 162 | + SILType i1 = SILType::getBuiltinIntegerType(1, ctx); |
| 163 | + return B.createIntegerLiteral(loc, i1, false); |
| 164 | + } |
| 165 | + |
| 166 | + // The variant-only availability-checking entrypoint is not part of the |
| 167 | + // Swift 5.0 ABI. It is only available in macOS 10.15 and above. |
| 168 | + bool isVariantEntrypointAvailable = !targetTriple.isMacOSXVersionLT(10, 15); |
| 169 | + |
| 170 | + // If there is no check for the target but there is for the variant, then we |
| 171 | + // only need to emit code for the variant check. |
| 172 | + if (isVariantEntrypointAvailable && targetVersion.isAll() && |
| 173 | + !variantVersion.isAll()) |
| 174 | + return emitOSVersionRangeCheck(SGF, loc, variantVersion, |
| 175 | + /*forVariant*/ true); |
| 176 | + |
| 177 | + // Similarly, if there is a check for the target but not for the target |
| 178 | + // variant then we only to emit code for the target check. |
| 179 | + if (!targetVersion.isAll() && variantVersion.isAll()) |
| 180 | + return emitOSVersionRangeCheck(SGF, loc, targetVersion, |
| 181 | + /*forVariant*/ false); |
| 182 | + |
| 183 | + if (!isVariantEntrypointAvailable || |
| 184 | + (!targetVersion.isAll() && !variantVersion.isAll())) { |
| 185 | + |
| 186 | + // If the variant-only entrypoint isn't available (as is the case |
| 187 | + // pre-macOS 10.15) we need to use the zippered entrypoint (which is part of |
| 188 | + // the Swift 5.0 ABI) even when the macOS version is '*' (all). In this |
| 189 | + // case, use the minimum macOS deployment version from the target triple. |
| 190 | + // This ensures the check always passes on macOS. |
| 191 | + if (!isVariantEntrypointAvailable && targetVersion.isAll()) { |
| 192 | + assert(targetTriple.isMacOSX()); |
| 193 | + |
| 194 | + llvm::VersionTuple macosVersion; |
| 195 | + targetTriple.getMacOSXVersion(macosVersion); |
| 196 | + targetVersion = VersionRange::allGTE(macosVersion); |
| 197 | + } |
| 198 | + |
| 199 | + return emitOSVersionOrVariantVersionRangeCheck(SGF, loc, targetVersion, |
| 200 | + variantVersion); |
| 201 | + } |
| 202 | + |
| 203 | + llvm_unreachable("Unhandled zippered configuration"); |
| 204 | +} |
| 205 | + |
23 | 206 | /// Given a value, extracts all elements to `result` from this value if it's a
|
24 | 207 | /// tuple. Otherwise, add this value directly to `result`.
|
25 | 208 | static void extractAllElements(SILValue val, SILLocation loc,
|
@@ -68,7 +251,7 @@ static SILValue emitZipperedBackDeployIfAvailableBooleanTestValue(
|
68 | 251 | VariantOSVersion = VersionRange::allGTE(*version);
|
69 | 252 | }
|
70 | 253 |
|
71 |
| - return SGF.emitZipperedOSVersionRangeCheck(loc, OSVersion, VariantOSVersion); |
| 254 | + return emitZipperedOSVersionRangeCheck(SGF, loc, OSVersion, VariantOSVersion); |
72 | 255 | }
|
73 | 256 |
|
74 | 257 | /// Emit the following branch SIL instruction:
|
@@ -108,7 +291,7 @@ static void emitBackDeployIfAvailableCondition(SILGenFunction &SGF,
|
108 | 291 | bool isMacCatalyst =
|
109 | 292 | tripleIsMacCatalystEnvironment(SGF.getASTContext().LangOpts.Target);
|
110 | 293 | booleanTestValue =
|
111 |
| - SGF.emitOSVersionRangeCheck(loc, OSVersion, isMacCatalyst); |
| 294 | + emitOSVersionRangeCheck(SGF, loc, OSVersion, isMacCatalyst); |
112 | 295 | }
|
113 | 296 |
|
114 | 297 | SGF.B.createCondBranch(loc, booleanTestValue, availableBB, unavailableBB);
|
@@ -203,6 +386,75 @@ static void emitBackDeployForwardApplyAndReturnOrThrow(
|
203 | 386 | SGF.B.createBranch(loc, SGF.ReturnDest.getBlock(), directResults);
|
204 | 387 | }
|
205 | 388 |
|
| 389 | +SILValue |
| 390 | +SILGenFunction::emitIfAvailableQuery(SILLocation loc, |
| 391 | + PoundAvailableInfo *availability) { |
| 392 | + auto &ctx = getASTContext(); |
| 393 | + SILValue result; |
| 394 | + |
| 395 | + // Creates a boolean literal for availability conditions that have been |
| 396 | + // evaluated at compile time. Automatically inverts the value for |
| 397 | + // `#unavailable` queries. |
| 398 | + auto createBooleanTestLiteral = [&](bool value) { |
| 399 | + SILType i1 = SILType::getBuiltinIntegerType(1, ctx); |
| 400 | + if (availability->isUnavailability()) |
| 401 | + value = !value; |
| 402 | + return B.createIntegerLiteral(loc, i1, value); |
| 403 | + }; |
| 404 | + |
| 405 | + auto versionRange = availability->getAvailableRange(); |
| 406 | + |
| 407 | + // The OS version might be left empty if availability checking was disabled. |
| 408 | + // Treat it as always-true in that case. |
| 409 | + assert(versionRange || ctx.LangOpts.DisableAvailabilityChecking); |
| 410 | + |
| 411 | + if (ctx.LangOpts.TargetVariant && !ctx.LangOpts.DisableAvailabilityChecking) { |
| 412 | + // We're building zippered, so we need to pass both macOS and iOS versions |
| 413 | + // to the the runtime version range check. At run time that check will |
| 414 | + // determine what kind of process this code is loaded into. In a macOS |
| 415 | + // process it will use the macOS version; in an macCatalyst process it will |
| 416 | + // use the iOS version. |
| 417 | + |
| 418 | + auto variantVersionRange = availability->getVariantAvailableRange(); |
| 419 | + assert(variantVersionRange); |
| 420 | + |
| 421 | + if (versionRange && variantVersionRange) { |
| 422 | + result = emitZipperedOSVersionRangeCheck(*this, loc, *versionRange, |
| 423 | + *variantVersionRange); |
| 424 | + } else { |
| 425 | + // Type checking did not fill in versions so as a fallback treat this |
| 426 | + // condition as trivially true. |
| 427 | + result = createBooleanTestLiteral(true); |
| 428 | + } |
| 429 | + |
| 430 | + return result; |
| 431 | + } |
| 432 | + |
| 433 | + if (!versionRange) { |
| 434 | + // Type checking did not fill in version so as a fallback treat this |
| 435 | + // condition as trivially true. |
| 436 | + result = createBooleanTestLiteral(true); |
| 437 | + } else if (versionRange->isAll()) { |
| 438 | + result = createBooleanTestLiteral(true); |
| 439 | + } else if (versionRange->isEmpty()) { |
| 440 | + result = createBooleanTestLiteral(false); |
| 441 | + } else { |
| 442 | + bool isMacCatalyst = tripleIsMacCatalystEnvironment(ctx.LangOpts.Target); |
| 443 | + result = emitOSVersionRangeCheck(*this, loc, versionRange.value(), |
| 444 | + isMacCatalyst); |
| 445 | + if (availability->isUnavailability()) { |
| 446 | + // If this is an unavailability check, invert the result |
| 447 | + // by emitting a call to Builtin.xor_Int1(lhs, -1). |
| 448 | + SILType i1 = SILType::getBuiltinIntegerType(1, ctx); |
| 449 | + SILValue minusOne = B.createIntegerLiteral(loc, i1, -1); |
| 450 | + result = |
| 451 | + B.createBuiltinBinaryFunction(loc, "xor", i1, i1, {result, minusOne}); |
| 452 | + } |
| 453 | + } |
| 454 | + |
| 455 | + return result; |
| 456 | +} |
| 457 | + |
206 | 458 | bool SILGenModule::requiresBackDeploymentThunk(ValueDecl *decl,
|
207 | 459 | ResilienceExpansion expansion) {
|
208 | 460 | auto &ctx = getASTContext();
|
|
0 commit comments