Skip to content

Commit 7b2256f

Browse files
committed
[android] Move the string and other tags in pointers to the second byte because Android enabled memory tagging
Starting with Android 11, AArch64 placed a tag in the top byte of pointers to allocations, which has been slowly rolling out to more devices and collides with Swift's tags. Moving these tags to the second byte works around this problem.
1 parent 541f772 commit 7b2256f

File tree

8 files changed

+133
-10
lines changed

8 files changed

+133
-10
lines changed

lib/IRGen/MetadataRequest.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2681,9 +2681,16 @@ emitMetadataAccessByMangledName(IRGenFunction &IGF, CanType type,
26812681
unsigned mangledStringSize;
26822682
std::tie(mangledString, mangledStringSize) =
26832683
IGM.getTypeRef(type, CanGenericSignature(), MangledTypeRefRole::Metadata);
2684-
2685-
assert(mangledStringSize < 0x80000000u
2686-
&& "2GB of mangled name ought to be enough for anyone");
2684+
2685+
// Android AArch64 reserves the top byte of the address for memory tagging
2686+
// since Android 11, so only use the bottom 23 bits to store this size
2687+
// and the 24th bit to signal that there is a size.
2688+
if (IGM.Triple.isAndroid() && IGM.Triple.getArch() == llvm::Triple::aarch64)
2689+
assert(mangledStringSize < 0x00800001u &&
2690+
"8MB of mangled name ought to be enough for Android AArch64");
2691+
else
2692+
assert(mangledStringSize < 0x80000000u &&
2693+
"2GB of mangled name ought to be enough for anyone");
26872694

26882695
// Get or create the cache variable if necessary.
26892696
auto cache = IGM.getAddrOfTypeMetadataDemanglingCacheVariable(type,
@@ -2753,6 +2760,21 @@ emitMetadataAccessByMangledName(IRGenFunction &IGF, CanType type,
27532760
auto contBB = subIGF.createBasicBlock("");
27542761
llvm::Value *comparison = subIGF.Builder.CreateICmpSLT(load,
27552762
llvm::ConstantInt::get(IGM.Int64Ty, 0));
2763+
2764+
// Check if the 24th bit is set on Android AArch64 and only instantiate the
2765+
// type metadata if it is, as otherwise it might be negative only because
2766+
// of the memory tag on Android.
2767+
if (IGM.Triple.isAndroid() &&
2768+
IGM.Triple.getArch() == llvm::Triple::aarch64) {
2769+
2770+
auto getBitAfterAndroidTag = subIGF.Builder.CreateAnd(
2771+
load, llvm::ConstantInt::get(IGM.Int64Ty, 0x0080000000000000));
2772+
auto checkNotAndroidTag = subIGF.Builder.CreateICmpNE(
2773+
getBitAfterAndroidTag, llvm::ConstantInt::get(IGM.Int64Ty, 0));
2774+
2775+
comparison = subIGF.Builder.CreateAnd(comparison, checkNotAndroidTag);
2776+
}
2777+
27562778
comparison = subIGF.Builder.CreateExpect(comparison,
27572779
llvm::ConstantInt::get(IGM.Int1Ty, 0));
27582780
subIGF.Builder.CreateCondBr(comparison, isUnfilledBB, contBB);

lib/IRGen/SwiftTargetInfo.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,17 @@ static void setToMask(SpareBitVector &bits, unsigned size, uint64_t mask) {
3636
/// Configures target-specific information for arm64 platforms.
3737
static void configureARM64(IRGenModule &IGM, const llvm::Triple &triple,
3838
SwiftTargetInfo &target) {
39-
setToMask(target.PointerSpareBits, 64,
40-
SWIFT_ABI_ARM64_SWIFT_SPARE_BITS_MASK);
41-
setToMask(target.ObjCPointerReservedBits, 64,
42-
SWIFT_ABI_ARM64_OBJC_RESERVED_BITS_MASK);
39+
if (triple.isAndroid()) {
40+
setToMask(target.PointerSpareBits, 64,
41+
SWIFT_ABI_ANDROID_ARM64_SWIFT_SPARE_BITS_MASK);
42+
setToMask(target.ObjCPointerReservedBits, 64,
43+
SWIFT_ABI_ANDROID_ARM64_OBJC_RESERVED_BITS_MASK);
44+
} else {
45+
setToMask(target.PointerSpareBits, 64,
46+
SWIFT_ABI_ARM64_SWIFT_SPARE_BITS_MASK);
47+
setToMask(target.ObjCPointerReservedBits, 64,
48+
SWIFT_ABI_ARM64_OBJC_RESERVED_BITS_MASK);
49+
}
4350
setToMask(target.IsObjCPointerBit, 64, SWIFT_ABI_ARM64_IS_OBJC_BIT);
4451

4552
if (triple.isOSDarwin()) {

stdlib/public/SwiftShims/HeapObject.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,22 @@ static_assert(alignof(HeapObject) == alignof(void*),
157157
#endif
158158
#define _swift_abi_SwiftSpareBitsMask \
159159
(__swift_uintptr_t) SWIFT_ABI_ARM64_SWIFT_SPARE_BITS_MASK
160+
#if defined(__ANDROID__)
161+
#define _swift_abi_ObjCReservedBitsMask \
162+
(__swift_uintptr_t) SWIFT_ABI_ANDROID_ARM64_OBJC_RESERVED_BITS_MASK
163+
#else
160164
#define _swift_abi_ObjCReservedBitsMask \
161165
(__swift_uintptr_t) SWIFT_ABI_ARM64_OBJC_RESERVED_BITS_MASK
166+
#endif
162167
#define _swift_abi_ObjCReservedLowBits \
163168
(unsigned) SWIFT_ABI_ARM64_OBJC_NUM_RESERVED_LOW_BITS
169+
#if defined(__ANDROID__)
170+
#define _swift_BridgeObject_TaggedPointerBits \
171+
(__swift_uintptr_t) SWIFT_ABI_DEFAULT_BRIDGEOBJECT_TAG_64 >> 8
172+
#else
164173
#define _swift_BridgeObject_TaggedPointerBits \
165174
(__swift_uintptr_t) SWIFT_ABI_DEFAULT_BRIDGEOBJECT_TAG_64
175+
#endif
166176

167177
#elif defined(__powerpc64__)
168178

stdlib/public/SwiftShims/System.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,19 @@
152152
/// Darwin reserves the low 4GB of address space.
153153
#define SWIFT_ABI_DARWIN_ARM64_LEAST_VALID_POINTER 0x100000000ULL
154154

155+
// Android AArch64 reserves the top byte for pointer tagging since Android 11,
156+
// so shift the spare bits tag to the second byte and zero the ObjC tag.
157+
#define SWIFT_ABI_ANDROID_ARM64_SWIFT_SPARE_BITS_MASK 0x00F0000000000007ULL
158+
#define SWIFT_ABI_ANDROID_ARM64_OBJC_RESERVED_BITS_MASK 0x0000000000000000ULL
159+
160+
#if defined(__ANDROID__) && defined(__aarch64__)
161+
#define SWIFT_ABI_ARM64_SWIFT_SPARE_BITS_MASK SWIFT_ABI_ANDROID_ARM64_SWIFT_SPARE_BITS_MASK
162+
#else
155163
// TBI guarantees the top byte of pointers is unused, but ARMv8.5-A
156164
// claims the bottom four bits of that for memory tagging.
157165
// Heap objects are eight-byte aligned.
158166
#define SWIFT_ABI_ARM64_SWIFT_SPARE_BITS_MASK 0xF000000000000007ULL
167+
#endif
159168

160169
// Objective-C reserves just the high bit for tagged pointers.
161170
#define SWIFT_ABI_ARM64_OBJC_RESERVED_BITS_MASK 0x8000000000000000ULL

stdlib/public/core/KeyPath.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,7 @@ internal struct KeyPathBuffer {
17641764
internal mutating func pushRaw(size: Int, alignment: Int)
17651765
-> UnsafeMutableRawBufferPointer {
17661766
var baseAddress = buffer.baseAddress.unsafelyUnwrapped
1767-
var misalign = Int(bitPattern: baseAddress) % alignment
1767+
var misalign = Int(bitPattern: baseAddress) & (alignment - 1)
17681768
if misalign != 0 {
17691769
misalign = alignment - misalign
17701770
baseAddress = baseAddress.advanced(by: misalign)
@@ -3242,7 +3242,7 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor {
32423242
) {
32433243
let alignment = MemoryLayout<T>.alignment
32443244
var baseAddress = destData.baseAddress.unsafelyUnwrapped
3245-
var misalign = Int(bitPattern: baseAddress) % alignment
3245+
var misalign = Int(bitPattern: baseAddress) & (alignment - 1)
32463246
if misalign != 0 {
32473247
misalign = alignment - misalign
32483248
baseAddress = baseAddress.advanced(by: misalign)

stdlib/public/core/SmallString.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
// ↑ ↑
2424
// first (leftmost) code unit discriminator (incl. count)
2525
//
26+
// On Android AArch64, there is one less byte available because the discriminator
27+
// is stored in the penultimate code unit instead, to match where it's stored
28+
// for large strings.
2629
@frozen @usableFromInline
2730
internal struct _SmallString {
2831
@usableFromInline
@@ -78,6 +81,8 @@ extension _SmallString {
7881
internal static var capacity: Int {
7982
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
8083
return 10
84+
#elseif os(Android) && arch(arm64)
85+
return 14
8186
#else
8287
return 15
8388
#endif
@@ -111,7 +116,11 @@ extension _SmallString {
111116
// usage: it always clears the discriminator and count (in case it's full)
112117
@inlinable @inline(__always)
113118
internal var zeroTerminatedRawCodeUnits: RawBitPattern {
119+
#if os(Android) && arch(arm64)
120+
let smallStringCodeUnitMask = ~UInt64(0xFFFF).bigEndian // zero last two bytes
121+
#else
114122
let smallStringCodeUnitMask = ~UInt64(0xFF).bigEndian // zero last byte
123+
#endif
115124
return (self._storage.0, self._storage.1 & smallStringCodeUnitMask)
116125
}
117126

stdlib/public/core/StringObject.swift

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
can compile to a fused check-and-branch, even if that burns part of the
5757
encoding space.
5858

59+
On Android AArch64, we cannot use the top byte for large strings because it is
60+
reserved by the OS for memory tagging since Android 11, so shift the
61+
discriminator to the second byte instead. This burns one more byte on small
62+
strings.
63+
5964
On 32-bit platforms, we store an explicit discriminator (as a UInt8) with the
6065
same encoding as above, placed in the high bits. E.g. `b62` above is in
6166
`_discriminator`'s `b6`.
@@ -111,8 +116,13 @@ internal struct _StringObject {
111116

112117
@inlinable @inline(__always)
113118
init(count: Int, variant: Variant, discriminator: UInt64, flags: UInt16) {
119+
#if os(Android) && arch(arm64)
120+
_internalInvariant(discriminator & 0x00FF_0000_0000_0000 == discriminator,
121+
"only the second byte can carry the discriminator and small count on Android AArch64")
122+
#else
114123
_internalInvariant(discriminator & 0xFF00_0000_0000_0000 == discriminator,
115124
"only the top byte can carry the discriminator and small count")
125+
#endif
116126

117127
self._count = count
118128
self._variant = variant
@@ -349,7 +359,13 @@ extension _StringObject.Nibbles {
349359
extension _StringObject.Nibbles {
350360
// Mask for address bits, i.e. non-discriminator and non-extra high bits
351361
@inlinable @inline(__always)
352-
static internal var largeAddressMask: UInt64 { return 0x0FFF_FFFF_FFFF_FFFF }
362+
static internal var largeAddressMask: UInt64 {
363+
#if os(Android) && arch(arm64)
364+
return 0xFF0F_FFFF_FFFF_FFFF
365+
#else
366+
return 0x0FFF_FFFF_FFFF_FFFF
367+
#endif
368+
}
353369

354370
// Mask for address bits, i.e. non-discriminator and non-extra high bits
355371
@inlinable @inline(__always)
@@ -360,20 +376,32 @@ extension _StringObject.Nibbles {
360376
// Discriminator for small strings
361377
@inlinable @inline(__always)
362378
internal static func small(isASCII: Bool) -> UInt64 {
379+
#if os(Android) && arch(arm64)
380+
return isASCII ? 0x00E0_0000_0000_0000 : 0x00A0_0000_0000_0000
381+
#else
363382
return isASCII ? 0xE000_0000_0000_0000 : 0xA000_0000_0000_0000
383+
#endif
364384
}
365385

366386
// Discriminator for small strings
367387
@inlinable @inline(__always)
368388
internal static func small(withCount count: Int, isASCII: Bool) -> UInt64 {
369389
_internalInvariant(count <= _SmallString.capacity)
390+
#if os(Android) && arch(arm64)
391+
return small(isASCII: isASCII) | UInt64(truncatingIfNeeded: count) &<< 48
392+
#else
370393
return small(isASCII: isASCII) | UInt64(truncatingIfNeeded: count) &<< 56
394+
#endif
371395
}
372396

373397
// Discriminator for large, immortal, swift-native strings
374398
@inlinable @inline(__always)
375399
internal static func largeImmortal() -> UInt64 {
400+
#if os(Android) && arch(arm64)
401+
return 0x0080_0000_0000_0000
402+
#else
376403
return 0x8000_0000_0000_0000
404+
#endif
377405
}
378406

379407
// Discriminator for large, mortal (i.e. managed), swift-native strings
@@ -397,15 +425,23 @@ extension _StringObject {
397425

398426
@inlinable @inline(__always)
399427
internal var isImmortal: Bool {
428+
#if os(Android) && arch(arm64)
429+
return (discriminatedObjectRawBits & 0x0080_0000_0000_0000) != 0
430+
#else
400431
return (discriminatedObjectRawBits & 0x8000_0000_0000_0000) != 0
432+
#endif
401433
}
402434

403435
@inlinable @inline(__always)
404436
internal var isMortal: Bool { return !isImmortal }
405437

406438
@inlinable @inline(__always)
407439
internal var isSmall: Bool {
440+
#if os(Android) && arch(arm64)
441+
return (discriminatedObjectRawBits & 0x0020_0000_0000_0000) != 0
442+
#else
408443
return (discriminatedObjectRawBits & 0x2000_0000_0000_0000) != 0
444+
#endif
409445
}
410446

411447
@inlinable @inline(__always)
@@ -419,7 +455,11 @@ extension _StringObject {
419455
// - Non-Cocoa shared strings
420456
@inlinable @inline(__always)
421457
internal var providesFastUTF8: Bool {
458+
#if os(Android) && arch(arm64)
459+
return (discriminatedObjectRawBits & 0x0010_0000_0000_0000) == 0
460+
#else
422461
return (discriminatedObjectRawBits & 0x1000_0000_0000_0000) == 0
462+
#endif
423463
}
424464

425465
@inlinable @inline(__always)
@@ -429,16 +469,26 @@ extension _StringObject {
429469
// conforms to `_AbstractStringStorage`
430470
@inline(__always)
431471
internal var hasStorage: Bool {
472+
#if os(Android) && arch(arm64)
473+
return (discriminatedObjectRawBits & 0x00F0_0000_0000_0000) == 0
474+
#else
432475
return (discriminatedObjectRawBits & 0xF000_0000_0000_0000) == 0
476+
#endif
433477
}
434478

435479
// Whether we are a mortal, native (tail-allocated) string
436480
@inline(__always)
437481
internal var hasNativeStorage: Bool {
482+
#if os(Android) && arch(arm64)
483+
// Android uses the same logic as explained below for other platforms,
484+
// except isSmall is at b53, so shift it to b61 first before proceeding.
485+
let bits = ~(discriminatedObjectRawBits << 8) & self._countAndFlagsBits
486+
#else
438487
// b61 on the object means isSmall, and on countAndFlags means
439488
// isNativelyStored. We just need to check that b61 is 0 on the object and 1
440489
// on countAndFlags.
441490
let bits = ~discriminatedObjectRawBits & self._countAndFlagsBits
491+
#endif
442492
let result = bits & 0x2000_0000_0000_0000 != 0
443493
_internalInvariant(!result || hasStorage, "native storage needs storage")
444494
return result
@@ -466,7 +516,11 @@ extension _StringObject {
466516
@inline(__always)
467517
internal var largeIsCocoa: Bool {
468518
_internalInvariant(isLarge)
519+
#if os(Android) && arch(arm64)
520+
return (discriminatedObjectRawBits & 0x0040_0000_0000_0000) != 0
521+
#else
469522
return (discriminatedObjectRawBits & 0x4000_0000_0000_0000) != 0
523+
#endif
470524
}
471525

472526
// Whether this string is in one of our fastest representations:
@@ -535,7 +589,11 @@ extension _StringObject {
535589

536590
@inlinable
537591
internal static func getSmallCount(fromRaw x: UInt64) -> Int {
592+
#if os(Android) && arch(arm64)
593+
return Int(truncatingIfNeeded: (x & 0x000F_0000_0000_0000) &>> 48)
594+
#else
538595
return Int(truncatingIfNeeded: (x & 0x0F00_0000_0000_0000) &>> 56)
596+
#endif
539597
}
540598

541599
@inlinable @inline(__always)
@@ -546,7 +604,11 @@ extension _StringObject {
546604

547605
@inlinable
548606
internal static func getSmallIsASCII(fromRaw x: UInt64) -> Bool {
607+
#if os(Android) && arch(arm64)
608+
return x & 0x0040_0000_0000_0000 != 0
609+
#else
549610
return x & 0x4000_0000_0000_0000 != 0
611+
#endif
550612
}
551613
@inlinable @inline(__always)
552614
internal var smallIsASCII: Bool {

stdlib/public/runtime/HeapObject.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ static inline bool isValidPointerForNativeRetain(const void *p) {
6666
// arm64_32 is special since it has 32-bit pointers but __arm64__ is true.
6767
// Catch it early since __POINTER_WIDTH__ is generally non-portable.
6868
return p != nullptr;
69+
#elif defined(__ANDROID__) && defined(__aarch64__)
70+
// Check the top of the second byte instead, since Android AArch64 reserves
71+
// the top byte for its own pointer tagging since Android 11.
72+
return (intptr_t)((uintptr_t)p << 8) > 0;
6973
#elif defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(__s390x__) || (defined(__powerpc64__) && defined(__LITTLE_ENDIAN__))
7074
// On these platforms, except s390x, the upper half of address space is reserved for the
7175
// kernel, so we can assume that pointer values in this range are invalid.

0 commit comments

Comments
 (0)