Skip to content

Commit f3feea5

Browse files
authored
Merge pull request #62103 from mikeash/custom-rr-abi
[Runtime] Add register-specific entrypoints for retain/release calls on ARM64.
2 parents cef0158 + b677b56 commit f3feea5

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed

include/swift/Runtime/CustomRRABI.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//===--- CustomRRABI.h - Custom retain/release ABI support ----------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Utilities for creating register-specific retain/release entrypoints.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_RUNTIME_CUSTOMRRABI_H
18+
#define SWIFT_RUNTIME_CUSTOMRRABI_H
19+
20+
namespace swift {
21+
22+
#if __arm64__ || defined(_M_ARM64)
23+
24+
// Invoke the macro X on the number of each register we support for a custom ABI
25+
// entrypoint, along with a custom parameter. We don't support all 31 registers:
26+
// - x0 is already covered by the standard entrypoints.
27+
// - x16/x17 are scratch registers that can be used by procedure call glue.
28+
// - x18 is reserved.
29+
// - x29 is the frame pointer.
30+
// - x30 is the link register and gets overwritten when making a call.
31+
#define CUSTOM_RR_ENTRYPOINTS_FOREACH_REG(X, param) \
32+
X(1, param) \
33+
X(2, param) \
34+
X(3, param) \
35+
X(4, param) \
36+
X(5, param) \
37+
X(6, param) \
38+
X(7, param) \
39+
X(8, param) \
40+
X(9, param) \
41+
X(10, param) \
42+
X(11, param) \
43+
X(12, param) \
44+
X(13, param) \
45+
X(14, param) \
46+
X(15, param) \
47+
X(19, param) \
48+
X(20, param) \
49+
X(21, param) \
50+
X(22, param) \
51+
X(23, param) \
52+
X(24, param) \
53+
X(25, param) \
54+
X(26, param) \
55+
X(27, param) \
56+
X(28, param)
57+
58+
// Helper template for deducing the parameter type of a one-parameter function.
59+
template <typename Ret, typename Param>
60+
Param returnTypeHelper(Ret (*)(Param)) {}
61+
62+
// Helper macro that defines one entrypoint that takes the parameter in reg and
63+
// calls through to function.
64+
#define CUSTOM_RR_ENTRYPOINTS_DEFINE_ONE_ENTRYPOINT(reg, function) \
65+
extern "C" SWIFT_RUNTIME_EXPORT decltype(function( \
66+
nullptr)) function##_x##reg() { \
67+
decltype(returnTypeHelper(function)) ptr; \
68+
asm(".ifnc %0, x" #reg "\n" \
69+
"mov %0, x" #reg "\n" \
70+
".endif" \
71+
: "=r"(ptr)); \
72+
return function(ptr); \
73+
}
74+
75+
// A macro that defines all register-specific entrypoints for the given
76+
// retain/release function.
77+
#define CUSTOM_RR_ENTRYPOINTS_DEFINE_ENTRYPOINTS(function) \
78+
CUSTOM_RR_ENTRYPOINTS_FOREACH_REG( \
79+
CUSTOM_RR_ENTRYPOINTS_DEFINE_ONE_ENTRYPOINT, function)
80+
81+
#else
82+
83+
// No custom entrypoints on other architectures.
84+
#define CUSTOM_RR_ENTRYPOINTS_DEFINE_ENTRYPOINTS(function)
85+
86+
#endif
87+
88+
} // namespace swift
89+
90+
#endif // SWIFT_RUNTIME_CUSTOMRRABI_H

stdlib/public/runtime/HeapObject.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "RuntimeInvocationsTracking.h"
2626
#include "WeakReference.h"
2727
#include "swift/Runtime/Debug.h"
28+
#include "swift/Runtime/CustomRRABI.h"
2829
#include "swift/Runtime/InstrumentsSupport.h"
2930
#include "swift/shims/GlobalObjects.h"
3031
#include "swift/shims/RuntimeShims.h"
@@ -359,6 +360,8 @@ HeapObject *swift::swift_retain(HeapObject *object) {
359360
#endif
360361
}
361362

363+
CUSTOM_RR_ENTRYPOINTS_DEFINE_ENTRYPOINTS(swift_retain)
364+
362365
SWIFT_RUNTIME_EXPORT
363366
HeapObject *(*SWIFT_RT_DECLARE_ENTRY _swift_retain)(HeapObject *object) =
364367
_swift_retain_;
@@ -412,6 +415,8 @@ void swift::swift_release(HeapObject *object) {
412415
#endif
413416
}
414417

418+
CUSTOM_RR_ENTRYPOINTS_DEFINE_ENTRYPOINTS(swift_release)
419+
415420
SWIFT_RUNTIME_EXPORT
416421
void (*SWIFT_RT_DECLARE_ENTRY _swift_release)(HeapObject *object) =
417422
_swift_release_;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#define NUM_REGS 30
2+
3+
// Apply `macro` to "all" registers. Skip x18 since it's reserved, and x30 since
4+
// it's the link register.
5+
#define ALL_REGS(macro) \
6+
macro( 0) \
7+
macro( 1) \
8+
macro( 2) \
9+
macro( 3) \
10+
macro( 4) \
11+
macro( 5) \
12+
macro( 6) \
13+
macro( 7) \
14+
macro( 8) \
15+
macro( 9) \
16+
macro(10) \
17+
macro(11) \
18+
macro(12) \
19+
macro(13) \
20+
macro(14) \
21+
macro(15) \
22+
macro(16) \
23+
macro(17) \
24+
macro(19) \
25+
macro(20) \
26+
macro(21) \
27+
macro(22) \
28+
macro(23) \
29+
macro(24) \
30+
macro(25) \
31+
macro(26) \
32+
macro(27) \
33+
macro(28) \
34+
macro(29)
35+
36+
// Apply `macro` with the given parameters to all registers that have
37+
// specialized entrypoints. That's the same as ALL_REGS, minus x0 (the standard
38+
// entrypoint covers that), x16/x17 (temporary registers used as linker glue),
39+
// and x29 (the link register).
40+
#define FUNCTION_REGS(macro, ...) \
41+
macro( 1, __VA_ARGS__) \
42+
macro( 2, __VA_ARGS__) \
43+
macro( 3, __VA_ARGS__) \
44+
macro( 4, __VA_ARGS__) \
45+
macro( 5, __VA_ARGS__) \
46+
macro( 6, __VA_ARGS__) \
47+
macro( 7, __VA_ARGS__) \
48+
macro( 8, __VA_ARGS__) \
49+
macro( 9, __VA_ARGS__) \
50+
macro(10, __VA_ARGS__) \
51+
macro(11, __VA_ARGS__) \
52+
macro(12, __VA_ARGS__) \
53+
macro(13, __VA_ARGS__) \
54+
macro(14, __VA_ARGS__) \
55+
macro(15, __VA_ARGS__) \
56+
macro(19, __VA_ARGS__) \
57+
macro(20, __VA_ARGS__) \
58+
macro(21, __VA_ARGS__) \
59+
macro(22, __VA_ARGS__) \
60+
macro(23, __VA_ARGS__) \
61+
macro(24, __VA_ARGS__) \
62+
macro(25, __VA_ARGS__) \
63+
macro(26, __VA_ARGS__) \
64+
macro(27, __VA_ARGS__) \
65+
macro(28, __VA_ARGS__)
66+
67+
// Apply `macro` to each function that gets specialized entrypoints. Also pass
68+
// 1 if the function is a retain variant, and 0 if it's a release variant.
69+
#define ALL_FUNCTIONS(macro) \
70+
macro(swift_retain, 1) \
71+
macro(swift_release, 0) \
72+
macro(swift_bridgeObjectRetain, 1) \
73+
macro(swift_bridgeObjectRelease, 0)
74+
75+
// Emit declarations for variables called xN stored in xN, initialized with
76+
// regs[N].
77+
#define PASS_REGS_HELPER(num) \
78+
register void *x ## num asm ("x" #num) = regs[num];
79+
#define PASS_REGS ALL_REGS(PASS_REGS_HELPER)
80+
81+
// Emit an entry in an asm inputs list containing "r" (xN).
82+
#define REG_INPUTS_HELPER(num) \
83+
"r" (x ## num),
84+
#define REG_INPUTS ALL_REGS(REG_INPUTS_HELPER)
85+
86+
// Make a function called call_function_xN that calls function_xN with registers
87+
// set to the contents of the given registers array.
88+
#define MAKE_CALL_FUNC(reg, func) \
89+
static inline void call_##func##_x##reg(void **regs) { \
90+
PASS_REGS \
91+
asm("bl _" #func "_x" #reg : : REG_INPUTS "i"(0)); \
92+
}
93+
94+
// Make a call_function_xN for each specialized function and register.
95+
#define MAKE_ALL_CALL_FUNCS(function, isRetain) \
96+
FUNCTION_REGS(MAKE_CALL_FUNC, function)
97+
ALL_FUNCTIONS(MAKE_ALL_CALL_FUNCS)
98+
99+
// Call `call` with each call_function_xN function created above, as well as the
100+
// base function name, the register it operates on, and whether it's a retain
101+
// function.
102+
static inline void foreachRRFunction(void (*call)(void (*)(void **regs),
103+
const char *name, int reg,
104+
int isRetain)) {
105+
#define CALL_ONE_FUNCTION(reg, function, isRetain) \
106+
call(call_##function##_x##reg, #function, reg, isRetain);
107+
#define CALL_WITH_FUNCTIONS(function, isRetain) \
108+
FUNCTION_REGS(CALL_ONE_FUNCTION, function, isRetain)
109+
110+
ALL_FUNCTIONS(CALL_WITH_FUNCTIONS)
111+
}

test/Runtime/custom_rr_abi.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// RUN: %target-run-simple-swift(-import-objc-header %S/Inputs/custom_rr_abi_utilities.h)
2+
3+
// REQUIRES: CPU=arm64 || CPU=arm64e
4+
5+
// REQUIRES: executable_test
6+
// UNSUPPORTED: use_os_stdlib
7+
// UNSUPPORTED: back_deployment_runtime
8+
9+
import StdlibUnittest
10+
11+
// A class that can provider a retainable pointer and determine whether it's
12+
// been retained or released. This creates a helper object that will be retained
13+
// or released. We don't attempt to clean up the helper so it leaks if not released,
14+
// but this is only used for this one test so that's OK.
15+
class RetainReleaseChecker {
16+
var pointerValue: UnsafeMutableRawPointer
17+
18+
private class Helper {}
19+
20+
private weak var weakRef: Helper?
21+
22+
private let originalRetainCount: UInt
23+
24+
init() {
25+
do {
26+
// Make a helper object, retain it so it stays alive, and put it into
27+
// pointerValue and weakRef.
28+
let helper = Helper()
29+
pointerValue = Unmanaged.passRetained(helper).toOpaque()
30+
weakRef = helper
31+
}
32+
// Record the original retain count before anything happens. Then we can
33+
// detect changes without needing to know exactly what the count is supposed
34+
// to be.
35+
originalRetainCount = _getRetainCount(weakRef!)
36+
}
37+
38+
// If helper was retained, then weakRef will still point to it, and the retain
39+
// count will have increased.
40+
var retained: Bool {
41+
weakRef != nil && _getRetainCount(weakRef!) > originalRetainCount
42+
}
43+
44+
// weakRef is the only reference we had to the helper, aside from the retain we put
45+
// on it to create pointerValue. If helper was released, then it will be destroyed
46+
// and weakRef will be nil.
47+
var released: Bool {
48+
weakRef == nil
49+
}
50+
}
51+
52+
var CustomRRABITestSuite = TestSuite("CustomRRABI")
53+
54+
CustomRRABITestSuite.test("retain") {
55+
foreachRRFunction { function, cname, register, isRetain in
56+
let name = String(cString: cname!)
57+
let fullname = "\(name)_x\(register)"
58+
59+
// Create a set of RR checker objects.
60+
var checkers = (0..<NUM_REGS).map{ _ in RetainReleaseChecker() }
61+
62+
// Fill out a registers array with the pointers from the RR checkers.
63+
var regs: [UnsafeMutableRawPointer?] = checkers.map{ $0.pointerValue }
64+
65+
// Call the RR function.
66+
function!(&regs)
67+
68+
// Make sure all the checkers report what they're supposed to. All registers
69+
// aside from `register` should be untouched, and `register` should have been
70+
// either retained or released.
71+
for (i, checker) in checkers.enumerated() {
72+
if i == register {
73+
if isRetain != 0 {
74+
expectTrue(checker.retained, "\(fullname) must retain x\(i)")
75+
} else {
76+
expectTrue(checker.released, "\(fullname) must release x\(i)")
77+
}
78+
} else {
79+
expectFalse(checker.retained, "\(fullname) must not retain x\(i)")
80+
expectFalse(checker.released, "\(fullname) must not retain x\(i)")
81+
}
82+
}
83+
}
84+
}
85+
86+
runAllTests()

0 commit comments

Comments
 (0)