Files
swift-mirror/lib/IRGen/SwiftTargetInfo.cpp
Mike Ash 93fae78e04 [IRGen][Runtime] Add emit-into-client retain/release calls for Darwin ARM64.
This is currently disabled by default. Building the client library can be enabled with the CMake option SWIFT_BUILD_CLIENT_RETAIN_RELEASE, and using the library can be enabled with the flags -Xfrontend -enable-client-retain-release.

To improve retain/release performance, we build a static library containing optimized implementations of the fast paths of swift_retain, swift_release, and the corresponding bridgeObject functions. This avoids going through a stub to make a cross-library call.

IRGen gains awareness of these new functions and emits calls to them when the functionality is enabled and the target supports them. Two options are added to force use of them on or off: -enable-client-retain-release and -disable-client-retain-release. When enabled, the compiler auto-links the static library containing the implementations.

The new calls also use LLVM's preserve_most calling convention. Since retain/release doesn't need a large number of scratch registers, this is mostly harmless for the implementation, while allowing callers to improve code size and performance by spilling fewer registers around refcounting calls. (Experiments with an even more aggressive calling convention preserving x2 and up showed an insignificant savings in code size, so preserve_most seems to be a good middle ground.)

Since the implementations are embedded into client binaries, any change in the runtime's refcounting implementation needs to stay compatible with this new fast path implementation. This is ensured by having the implementation use a runtime-provided mask to check whether it can proceed into its fast path. The mask is provided as the address of the absolute symbol _swift_retainRelease_slowpath_mask_v1. If that mask ANDed with the object's current refcount field is non-zero, then we take the slow path. A future runtime that changes the refcounting implementation can adjust this mask to match, or set the mask to all 1s to disable the old embedded fast path entirely (as long as the new representation never uses 0 as a valid refcount field value).

As part of this work, the overall approach for bridgeObjectRetain is changed slightly. Previously, it would mask off the spare bits from the native pointer and then call through to swift_retain. This either lost the spare bits in the return value (when tail calling swift_retain) which is problematic since it's supposed to return its parameter, or it required pushing a stack frame which is inefficient. Now, swift_retain takes on the responsibility of masking off spare bits from the parameter and preserving them in the return value. This is a trivial addition to the fast path (just a quick mask and an extra register for saving the original value) and makes bridgeObjectRetain quite a bit more efficient when implemented correctly to return the exact value it was passed.

The runtime's implementations of swift_retain/release are now also marked as preserve_most so that they can be tail called from the client library. preserve_most is compatible with callers expecting the standard calling convention so this doesn't break any existing clients. Some ugly tricks were needed to prevent the compiler from creating unnecessary stack frames with the new calling convention. Avert your eyes.

To allow back deployment, the runtime now has aliases for these functions called swift_retain_preservemost and swift_release_preservemost. The client library brings weak definitions of these functions that save the extra registers and call through to swift_retain/release. This allows them to work correctly on older runtimes, with a small performance penalty, while still running at full speed on runtimes that have the new preservemost symbols.

Although this is only supported on Darwin at the moment, it shouldn't be too much work to adapt it to other ARM64 targets. We need to ensure the assembly plays nice with the other platforms' assemblers, and make sure the implementation is correct for the non-ObjC-interop case.

rdar://122595871
2025-10-27 12:00:28 -04:00

288 lines
10 KiB
C++

//===--- SwiftTargetInfo.cpp ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines the SwiftTargetInfo abstract base class. This class
// provides an interface to target-dependent attributes of interest to Swift.
//
//===----------------------------------------------------------------------===//
#include "SwiftTargetInfo.h"
#include "IRGenModule.h"
#include "llvm/TargetParser/Triple.h"
#include "llvm/IR/DataLayout.h"
#include "swift/ABI/System.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/IRGenOptions.h"
#include "swift/Basic/Platform.h"
using namespace swift;
using namespace irgen;
/// Initialize a bit vector to be equal to the given bit-mask.
static void setToMask(SpareBitVector &bits, unsigned size, uint64_t mask) {
bits.clear();
bits.add(size, mask);
}
/// Configures target-specific information for arm64 platforms.
static void configureARM64(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
if (triple.isAndroid()) {
setToMask(target.PointerSpareBits, 64,
SWIFT_ABI_ANDROID_ARM64_SWIFT_SPARE_BITS_MASK);
setToMask(target.ObjCPointerReservedBits, 64,
SWIFT_ABI_ANDROID_ARM64_OBJC_RESERVED_BITS_MASK);
} else {
setToMask(target.PointerSpareBits, 64,
SWIFT_ABI_ARM64_SWIFT_SPARE_BITS_MASK);
setToMask(target.ObjCPointerReservedBits, 64,
SWIFT_ABI_ARM64_OBJC_RESERVED_BITS_MASK);
}
setToMask(target.IsObjCPointerBit, 64, SWIFT_ABI_ARM64_IS_OBJC_BIT);
// Non-embedded Darwin reserves the low 4GB of address space.
if (triple.isOSDarwin() &&
!IGM.getSwiftModule()->getASTContext().LangOpts.hasFeature(
Feature::Embedded)) {
target.LeastValidPointerValue =
SWIFT_ABI_DARWIN_ARM64_LEAST_VALID_POINTER;
}
// arm64 has no special objc_msgSend variants, not even stret.
target.ObjCUseStret = false;
// arm64 requires marker assembly for objc_retainAutoreleasedReturnValue.
target.ObjCRetainAutoreleasedReturnValueMarker =
"mov\tfp, fp\t\t// marker for objc_retainAutoreleaseReturnValue";
// arm64 requires ISA-masking.
target.ObjCUseISAMask = true;
// arm64 tops out at 56 effective bits of address space and reserves the high
// half for the kernel.
target.SwiftRetainIgnoresNegativeValues = true;
// ARM64 Darwin has swiftClientRetainRelease, but not in Embedded mode. JIT
// mode can't load the static library, so disable it there as well.
if (triple.isOSDarwin() &&
!IGM.getSwiftModule()->getASTContext().LangOpts.hasFeature(
Feature::Embedded) &&
!IGM.getOptions().UseJIT)
target.HasSwiftClientRRLibrary = true;
target.UsableSwiftAsyncContextAddrIntrinsic = true;
}
/// Configures target-specific information for arm64_32 platforms.
static void configureARM64_32(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 32,
SWIFT_ABI_ARM_SWIFT_SPARE_BITS_MASK);
// arm64_32 has no special objc_msgSend variants, not even stret.
target.ObjCUseStret = false;
// arm64_32 requires marker assembly for objc_retainAutoreleasedReturnValue.
target.ObjCRetainAutoreleasedReturnValueMarker =
"mov\tfp, fp\t\t// marker for objc_retainAutoreleaseReturnValue";
setToMask(target.IsObjCPointerBit, 32, SWIFT_ABI_ARM_IS_OBJC_BIT);
target.ObjCHasOpaqueISAs = true;
}
/// Configures target-specific information for x86-64 platforms.
static void configureX86_64(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 64,
SWIFT_ABI_X86_64_SWIFT_SPARE_BITS_MASK);
setToMask(target.IsObjCPointerBit, 64, SWIFT_ABI_X86_64_IS_OBJC_BIT);
if (triple.isSimulatorEnvironment()) {
setToMask(target.ObjCPointerReservedBits, 64,
SWIFT_ABI_X86_64_SIMULATOR_OBJC_RESERVED_BITS_MASK);
} else {
setToMask(target.ObjCPointerReservedBits, 64,
SWIFT_ABI_X86_64_OBJC_RESERVED_BITS_MASK);
}
if (triple.isOSDarwin() &&
!IGM.getSwiftModule()->getASTContext().LangOpts.hasFeature(
Feature::Embedded)) {
target.LeastValidPointerValue =
SWIFT_ABI_DARWIN_X86_64_LEAST_VALID_POINTER;
}
// x86-64 has every objc_msgSend variant known to humankind.
target.ObjCUseFPRet = true;
target.ObjCUseFP2Ret = true;
// x86-64 requires ISA-masking.
target.ObjCUseISAMask = true;
// x86-64 only has 48 effective bits of address space and reserves the high
// half for the kernel.
target.SwiftRetainIgnoresNegativeValues = true;
target.UsableSwiftAsyncContextAddrIntrinsic = true;
}
/// Configures target-specific information for 32-bit x86 platforms.
static void configureX86(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 32,
SWIFT_ABI_I386_SWIFT_SPARE_BITS_MASK);
// x86 uses objc_msgSend_fpret but not objc_msgSend_fp2ret.
target.ObjCUseFPRet = true;
setToMask(target.IsObjCPointerBit, 32, SWIFT_ABI_I386_IS_OBJC_BIT);
}
/// Configures target-specific information for 32-bit arm platforms.
static void configureARM(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 32,
SWIFT_ABI_ARM_SWIFT_SPARE_BITS_MASK);
// ARM requires marker assembly for objc_retainAutoreleasedReturnValue.
target.ObjCRetainAutoreleasedReturnValueMarker =
"mov\tr7, r7\t\t// marker for objc_retainAutoreleaseReturnValue";
// armv7k has opaque ISAs which must go through the ObjC runtime.
if (triple.getSubArch() == llvm::Triple::SubArchType::ARMSubArch_v7k)
target.ObjCHasOpaqueISAs = true;
setToMask(target.IsObjCPointerBit, 32, SWIFT_ABI_ARM_IS_OBJC_BIT);
}
/// Configures target-specific information for powerpc platforms.
static void configurePowerPC(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 32,
SWIFT_ABI_POWERPC_SWIFT_SPARE_BITS_MASK);
}
/// Configures target-specific information for powerpc64 platforms.
static void configurePowerPC64(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 64,
SWIFT_ABI_POWERPC64_SWIFT_SPARE_BITS_MASK);
}
/// Configures target-specific information for SystemZ platforms.
static void configureSystemZ(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
setToMask(target.PointerSpareBits, 64,
SWIFT_ABI_S390X_SWIFT_SPARE_BITS_MASK);
setToMask(target.ObjCPointerReservedBits, 64,
SWIFT_ABI_S390X_OBJC_RESERVED_BITS_MASK);
setToMask(target.IsObjCPointerBit, 64, SWIFT_ABI_S390X_IS_OBJC_BIT);
target.SwiftRetainIgnoresNegativeValues = true;
}
/// Configures target-specific information for wasm32 platforms.
static void configureWasm32(IRGenModule &IGM, const llvm::Triple &triple,
SwiftTargetInfo &target) {
target.LeastValidPointerValue =
SWIFT_ABI_WASM32_LEAST_VALID_POINTER;
}
/// Configure a default target.
SwiftTargetInfo::SwiftTargetInfo(
llvm::Triple::ObjectFormatType outputObjectFormat,
unsigned numPointerBits)
: OutputObjectFormat(outputObjectFormat),
HeapObjectAlignment(numPointerBits / 8),
LeastValidPointerValue(SWIFT_ABI_DEFAULT_LEAST_VALID_POINTER)
{
setToMask(PointerSpareBits, numPointerBits,
SWIFT_ABI_DEFAULT_SWIFT_SPARE_BITS_MASK);
setToMask(ObjCPointerReservedBits, numPointerBits,
SWIFT_ABI_DEFAULT_OBJC_RESERVED_BITS_MASK);
setToMask(FunctionPointerSpareBits, numPointerBits,
SWIFT_ABI_DEFAULT_FUNCTION_SPARE_BITS_MASK);
if (numPointerBits == 64) {
ReferencePoisonDebugValue =
SWIFT_ABI_DEFAULT_REFERENCE_POISON_DEBUG_VALUE_64;
} else {
ReferencePoisonDebugValue =
SWIFT_ABI_DEFAULT_REFERENCE_POISON_DEBUG_VALUE_32;
}
}
SwiftTargetInfo SwiftTargetInfo::get(IRGenModule &IGM) {
const llvm::Triple &triple = IGM.Context.LangOpts.Target;
auto pointerSize = IGM.DataLayout.getPointerSizeInBits();
// Prepare generic target information.
SwiftTargetInfo target(triple.getObjectFormat(), pointerSize);
// On Apple platforms, we implement "once" using dispatch_once,
// which exposes a barrier-free inline path with -1 as the "done" value.
if (triple.isOSDarwin())
target.OnceDonePredicateValue = -1L;
// Other platforms use std::call_once() and we don't
// assume that they have a barrier-free inline fast path.
switch (triple.getArch()) {
case llvm::Triple::x86_64:
configureX86_64(IGM, triple, target);
break;
case llvm::Triple::x86:
configureX86(IGM, triple, target);
break;
case llvm::Triple::arm:
case llvm::Triple::thumb:
configureARM(IGM, triple, target);
break;
case llvm::Triple::aarch64:
case llvm::Triple::aarch64_32:
if (triple.getArchName() == "arm64_32")
configureARM64_32(IGM, triple, target);
else
configureARM64(IGM, triple, target);
break;
case llvm::Triple::ppc:
configurePowerPC(IGM, triple, target);
break;
case llvm::Triple::ppc64:
case llvm::Triple::ppc64le:
configurePowerPC64(IGM, triple, target);
break;
case llvm::Triple::systemz:
configureSystemZ(IGM, triple, target);
break;
case llvm::Triple::wasm32:
configureWasm32(IGM, triple, target);
break;
default:
// FIXME: Complain here? Default target info is unlikely to be correct.
break;
}
if (IGM.getOptions().CustomLeastValidPointerValue != 0)
target.LeastValidPointerValue = IGM.getOptions().CustomLeastValidPointerValue;
return target;
}
bool SwiftTargetInfo::hasObjCTaggedPointers() const {
return ObjCPointerReservedBits.any();
}