Skip to content

Commit ce4ba6c

Browse files
committed
[Test] Simplified registration.
At the cost of adding an unsafe bitcast implementation detail, simplified the code involved to register a new FunctionTest when adding one. Also simplifies how Swift native `FunctionTest`s are registered with the C++ registry. Now, the to-be-executed thin closure for native Swift `FunctionTest`s is stored under within the swift::test::FunctionTest instance corresponding to it. Because its type isn't representable in C++, `void *` is used instead. When the FunctionTest is invoked, a thunk is called which takes the actual test function and bridged versions of the arguments. That thunk unwraps the arguments, casts the stored function to the appropriate type, and invokes it. Thanks to Andrew Trick for the idea.
1 parent 36805c8 commit ce4ba6c

File tree

5 files changed

+90
-41
lines changed

5 files changed

+90
-41
lines changed

SwiftCompilerSources/Sources/SIL/Test.swift

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// }
1818
// - In SwiftCompilerSources/Sources/SIL/Test.swift's registerSILTests function,
1919
// register the new test:
20-
// registerFunctionTest(myNewTest, implementation: { myNewTest.run($0, $1, $2) })
20+
// registerFunctionTest(myNewTest)
2121
//
2222
//===----------------------------------------------------------------------===//
2323
//
@@ -90,22 +90,21 @@
9090
import Basic
9191
import SILBridging
9292

93+
/// The primary interface to in-IR tests.
9394
public struct FunctionTest {
94-
public var name: String
95-
public typealias FunctionTestImplementation = (Function, TestArguments, TestContext) -> ()
96-
private var implementation: FunctionTestImplementation
97-
init(name: String, implementation: @escaping FunctionTestImplementation) {
95+
let name: String
96+
let invocation: FunctionTestInvocation
97+
98+
public init(name: String, invocation: @escaping FunctionTestInvocation) {
9899
self.name = name
99-
self.implementation = implementation
100-
}
101-
fileprivate func run(
102-
_ function: BridgedFunction,
103-
_ arguments: BridgedTestArguments,
104-
_ test: BridgedTestContext) {
105-
implementation(function.function, arguments.native, test.native)
100+
self.invocation = invocation
106101
}
107102
}
108103

104+
/// The type of the closure passed to a FunctionTest.
105+
public typealias FunctionTestInvocation = @convention(thin) (Function, TestArguments, TestContext) -> ()
106+
107+
/// Wraps the arguments specified in the specify_test instruction.
109108
public struct TestArguments {
110109
public var bridged: BridgedTestArguments
111110
fileprivate init(bridged: BridgedTestArguments) {
@@ -128,6 +127,7 @@ extension BridgedTestArguments {
128127
public var native: TestArguments { TestArguments(bridged: self) }
129128
}
130129

130+
/// An interface to the various analyses that are available.
131131
public struct TestContext {
132132
public var bridged: BridgedTestContext
133133
fileprivate init(bridged: BridgedTestContext) {
@@ -139,16 +139,52 @@ extension BridgedTestContext {
139139
public var native: TestContext { TestContext(bridged: self) }
140140
}
141141

142-
private func registerFunctionTest(
143-
_ test: FunctionTest,
144-
implementation: BridgedFunctionTestThunk) {
142+
/// Registration of each test in the SIL module.
143+
public func registerSILTests() {
144+
// Register each test.
145+
registerFunctionTest(parseTestSpecificationTest)
146+
147+
// Finally register the thunk they all call through.
148+
registerFunctionTestThunk(functionTestThunk)
149+
}
150+
151+
152+
private func registerFunctionTest(_ test: FunctionTest) {
145153
test.name._withStringRef { ref in
146-
registerFunctionTest(ref, implementation)
154+
registerFunctionTest(ref, eraseInvocation(test.invocation))
147155
}
148156
}
149157

150-
public func registerSILTests() {
151-
registerFunctionTest(parseTestSpecificationTest, implementation: { parseTestSpecificationTest.run($0, $1, $2) })
158+
/// The function called by the swift::test::FunctionTest which invokes the
159+
/// actual test function.
160+
///
161+
/// This function is necessary because tests need to be written in terms of
162+
/// native Swift types (Function, TestArguments, TestContext) rather than their
163+
/// bridged variants, but such a function isn't representable in C++. This
164+
/// thunk unwraps the bridged types and invokes the real function.
165+
private func functionTestThunk(
166+
_ erasedInvocation: UnsafeMutableRawPointer,
167+
_ function: BridgedFunction,
168+
_ arguments: BridgedTestArguments,
169+
_ context: BridgedTestContext) {
170+
let invocation = uneraseInvocation(erasedInvocation)
171+
invocation(function.function, arguments.native, context.native)
172+
}
173+
174+
/// Bitcast a thin test closure to void *.
175+
///
176+
/// Needed so that the closure can be represented in C++ for storage in the test
177+
/// registry.
178+
private func eraseInvocation(_ invocation: FunctionTestInvocation) -> UnsafeMutableRawPointer {
179+
return unsafeBitCast(invocation, to: UnsafeMutableRawPointer.self)
180+
}
181+
182+
/// Bitcast a void * to a thin test closure.
183+
///
184+
/// Needed so that the closure stored in the C++ test registry can be invoked
185+
/// via the functionTestThunk.
186+
private func uneraseInvocation(_ erasedInvocation: UnsafeMutableRawPointer) -> FunctionTestInvocation {
187+
return unsafeBitCast(erasedInvocation, to: FunctionTestInvocation.self)
152188
}
153189

154190
// Arguments:

include/swift/SIL/SILBridging.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,10 @@ struct Arguments;
15271527
class FunctionTest;
15281528
} // namespace swift::test
15291529

1530+
struct BridgedFunctionTest {
1531+
swift::test::FunctionTest *_Nonnull test;
1532+
};
1533+
15301534
struct BridgedTestArguments {
15311535
swift::test::Arguments *_Nonnull arguments;
15321536

@@ -1562,10 +1566,15 @@ struct BridgedTestContext {
15621566
swift::SwiftPassInvocation *_Nonnull invocation;
15631567
};
15641568

1565-
typedef void (*_Nonnull BridgedFunctionTestThunk)(BridgedFunction,
1566-
BridgedTestArguments,
1567-
BridgedTestContext);
1568-
void registerFunctionTest(llvm::StringRef, BridgedFunctionTestThunk thunk);
1569+
using SwiftNativeFunctionTestThunk = void (*_Nonnull)(void *_Nonnull,
1570+
BridgedFunction,
1571+
BridgedTestArguments,
1572+
BridgedTestContext);
1573+
1574+
void registerFunctionTestThunk(SwiftNativeFunctionTestThunk);
1575+
1576+
void registerFunctionTest(llvm::StringRef,
1577+
void *_Nonnull nativeSwiftInvocation);
15691578

15701579
SWIFT_END_NULLABILITY_ANNOTATIONS
15711580

include/swift/SIL/Test.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,11 @@ class FunctionTest final {
130130
/// values such as the results of analyses
131131
using Invocation = void (*)(SILFunction &, Arguments &, FunctionTest &);
132132

133-
using InvocationWithContext = void (*)(SILFunction &, Arguments &,
134-
FunctionTest &, void *);
133+
using NativeSwiftInvocation = void *;
135134

136135
private:
137136
/// The lambda to be run.
138-
TaggedUnion<Invocation, std::pair<InvocationWithContext, void *>> invocation;
137+
TaggedUnion<Invocation, NativeSwiftInvocation> invocation;
139138

140139
public:
141140
/// Creates a test that will run \p invocation and stores it in the global
@@ -151,7 +150,7 @@ class FunctionTest final {
151150
/// } // end namespace swift::test
152151
FunctionTest(StringRef name, Invocation invocation);
153152

154-
FunctionTest(StringRef name, void *context, InvocationWithContext invocation);
153+
FunctionTest(StringRef name, NativeSwiftInvocation invocation);
155154

156155
/// Computes and returns the function's dominance tree.
157156
DominanceInfo *getDominanceInfo();

lib/SIL/Utils/SILBridging.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,8 @@ void registerBridgedClass(StringRef className, SwiftMetatype metatype) {
133133
// Test
134134
//===----------------------------------------------------------------------===//
135135

136-
void registerFunctionTest(llvm::StringRef name,
137-
BridgedFunctionTestThunk thunk) {
138-
new swift::test::FunctionTest(
139-
name, reinterpret_cast<void *>(thunk),
140-
[](auto &function, auto &args, auto &test, void *ctx) {
141-
auto thunk = reinterpret_cast<BridgedFunctionTestThunk>(ctx);
142-
thunk({&function}, {&args}, test.getContext());
143-
});
136+
void registerFunctionTest(llvm::StringRef name, void *nativeSwiftInvocation) {
137+
new swift::test::FunctionTest(name, nativeSwiftInvocation);
144138
}
145139

146140
bool BridgedTestArguments::hasUntaken() const {

lib/SIL/Utils/Test.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace {
2626

2727
class Registry {
2828
DenseMap<StringRef, FunctionTest *> registeredTests;
29+
SwiftNativeFunctionTestThunk thunk;
2930

3031
public:
3132
static Registry &get() {
@@ -39,6 +40,12 @@ class Registry {
3940
(void)inserted;
4041
}
4142

43+
void registerFunctionTestThunk(SwiftNativeFunctionTestThunk thunk) {
44+
this->thunk = thunk;
45+
}
46+
47+
SwiftNativeFunctionTestThunk getFunctionTestThunk() { return thunk; }
48+
4249
FunctionTest *getFunctionTest(StringRef name) {
4350
auto *res = registeredTests[name];
4451
if (!res) {
@@ -62,15 +69,18 @@ class Registry {
6269

6370
} // end anonymous namespace
6471

72+
void registerFunctionTestThunk(SwiftNativeFunctionTestThunk thunk) {
73+
Registry::get().registerFunctionTestThunk(thunk);
74+
}
75+
6576
FunctionTest::FunctionTest(StringRef name, Invocation invocation)
6677
: invocation(invocation), pass(nullptr), function(nullptr),
6778
dependencies(nullptr) {
6879
Registry::get().registerFunctionTest(this, name);
6980
}
70-
FunctionTest::FunctionTest(StringRef name, void *context,
71-
InvocationWithContext invocation)
72-
: invocation(std::make_pair(invocation, context)), pass(nullptr),
73-
function(nullptr), dependencies(nullptr) {
81+
FunctionTest::FunctionTest(StringRef name, NativeSwiftInvocation invocation)
82+
: invocation(invocation), pass(nullptr), function(nullptr),
83+
dependencies(nullptr) {
7484
Registry::get().registerFunctionTest(this, name);
7585
}
7686

@@ -84,11 +94,12 @@ void FunctionTest::run(SILFunction &function, Arguments &arguments,
8494
this->function = &function;
8595
this->dependencies = &dependencies;
8696
if (invocation.isa<Invocation>()) {
87-
auto fn = this->invocation.get<Invocation>();
97+
auto fn = invocation.get<Invocation>();
8898
fn(function, arguments, *this);
8999
} else {
90-
auto pair = invocation.get<std::pair<InvocationWithContext, void *>>();
91-
pair.first(function, arguments, *this, pair.second);
100+
auto *fn = invocation.get<NativeSwiftInvocation>();
101+
Registry::get().getFunctionTestThunk()(fn, {&function}, {&arguments},
102+
getContext());
92103
}
93104
this->pass = nullptr;
94105
this->function = nullptr;

0 commit comments

Comments
 (0)