Skip to content

Commit cc8ae1c

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 b4e3d05 commit cc8ae1c

File tree

8 files changed

+139
-8
lines changed

8 files changed

+139
-8
lines changed

lib/IRGen/MetadataRequest.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,9 +2659,16 @@ emitMetadataAccessByMangledName(IRGenFunction &IGF, CanType type,
26592659
unsigned mangledStringSize;
26602660
std::tie(mangledString, mangledStringSize) =
26612661
IGM.getTypeRef(type, CanGenericSignature(), MangledTypeRefRole::Metadata);
2662-
2663-
assert(mangledStringSize < 0x80000000u
2664-
&& "2GB of mangled name ought to be enough for anyone");
2662+
2663+
// Android AArch64 reserves the top byte of the address for memory tagging
2664+
// since Android 11, so only use the bottom 23 bits to store this size
2665+
// and the 24th bit to signal that there is a size.
2666+
if (IGM.Triple.isAndroid() && IGM.Triple.getArch() == llvm::Triple::aarch64)
2667+
assert(mangledStringSize < 0x00800001u &&
2668+
"8MB of mangled name ought to be enough for Android AArch64");
2669+
else
2670+
assert(mangledStringSize < 0x80000000u &&
2671+
"2GB of mangled name ought to be enough for anyone");
26652672

26662673
// Get or create the cache variable if necessary.
26672674
auto cache = IGM.getAddrOfTypeMetadataDemanglingCacheVariable(type,
@@ -2731,6 +2738,21 @@ emitMetadataAccessByMangledName(IRGenFunction &IGF, CanType type,
27312738
auto contBB = subIGF.createBasicBlock("");
27322739
llvm::Value *comparison = subIGF.Builder.CreateICmpSLT(load,
27332740
llvm::ConstantInt::get(IGM.Int64Ty, 0));
2741+
2742+
// Check if the 24th bit is set on Android AArch64 and only instantiate the
2743+
// type metadata if it is, as otherwise it might be negative only because
2744+
// of the memory tag on Android.
2745+
if (IGM.Triple.isAndroid() &&
2746+
IGM.Triple.getArch() == llvm::Triple::aarch64) {
2747+
2748+
auto getBitAfterAndroidTag = subIGF.Builder.CreateAnd(
2749+
load, llvm::ConstantInt::get(IGM.Int64Ty, 0x0080000000000000));
2750+
auto checkNotAndroidTag = subIGF.Builder.CreateICmpNE(
2751+
getBitAfterAndroidTag, llvm::ConstantInt::get(IGM.Int64Ty, 0));
2752+
2753+
comparison = subIGF.Builder.CreateAnd(comparison, checkNotAndroidTag);
2754+
}
2755+
27342756
comparison = subIGF.Builder.CreateExpect(comparison,
27352757
llvm::ConstantInt::get(IGM.Int1Ty, 0));
27362758
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,11 @@ internal struct KeyPathBuffer {
17641764
internal mutating func pushRaw(size: Int, alignment: Int)
17651765
-> UnsafeMutableRawBufferPointer {
17661766
var baseAddress = buffer.baseAddress.unsafelyUnwrapped
1767+
#if os(Android) && arch(arm64)
1768+
var misalign = Int(bitPattern: baseAddress) & (alignment - 1)
1769+
#else
17671770
var misalign = Int(bitPattern: baseAddress) % alignment
1771+
#endif
17681772
if misalign != 0 {
17691773
misalign = alignment - misalign
17701774
baseAddress = baseAddress.advanced(by: misalign)
@@ -3242,7 +3246,11 @@ internal struct InstantiateKeyPathBuffer: KeyPathPatternVisitor {
32423246
) {
32433247
let alignment = MemoryLayout<T>.alignment
32443248
var baseAddress = destData.baseAddress.unsafelyUnwrapped
3249+
#if os(Android) && arch(arm64)
3250+
var misalign = Int(bitPattern: baseAddress) & (alignment - 1)
3251+
#else
32453252
var misalign = Int(bitPattern: baseAddress) % alignment
3253+
#endif
32463254
if misalign != 0 {
32473255
misalign = alignment - misalign
32483256
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)