Skip to content

Commit 954c0f5

Browse files
Merge pull request #68813 from nate-chandler/swiftcompilersources/test-bridging
[SwiftCompilerSources] Bridged in-IR testing.
2 parents 25c1418 + 36805c8 commit 954c0f5

File tree

9 files changed

+374
-105
lines changed

9 files changed

+374
-105
lines changed

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public func initializeSwiftModules() {
2020
registerSwiftAnalyses()
2121
registerSwiftPasses()
2222
initializeSwiftParseModules()
23+
registerSILTests()
2324
}
2425

2526
private func registerPass(

SwiftCompilerSources/Sources/SIL/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ add_swift_compiler_module(SIL
2323
Registration.swift
2424
SmallProjectionPath.swift
2525
SubstitutionMap.swift
26+
Test.swift
2627
Type.swift
2728
Utils.swift
2829
Value.swift
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//===----------- Test.swift - In-IR tests from Swift source ---------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 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+
// TO ADD A NEW TEST, just add a new FunctionTest instance.
14+
// - In the source file containing the functionality you want to test:
15+
// let myNewTest =
16+
// FunctionTest("my_new_test") { function, arguments, context in
17+
// }
18+
// - In SwiftCompilerSources/Sources/SIL/Test.swift's registerSILTests function,
19+
// register the new test:
20+
// registerFunctionTest(myNewTest, implementation: { myNewTest.run($0, $1, $2) })
21+
//
22+
//===----------------------------------------------------------------------===//
23+
//
24+
// Provides a mechanism for writing tests against compiler code in the context
25+
// of a function. The goal is to get the same effect as calling a function and
26+
// checking its output.
27+
//
28+
// This is done via the specify_test instruction. Using one or more instances
29+
// of it in your test case's SIL function, you can specify which test (instance
30+
// of FunctionTest) should be run and what arguments should be provided to it.
31+
// The test grabs the arguments it expects out of the TestArguments instance
32+
// it is provided. It calls some function or functions. It then prints out
33+
// interesting results. These results can then be FileCheck'd.
34+
//
35+
// CASE STUDY:
36+
// Here's an example of how it works:
37+
// 0) A source file, NeatUtils.cpp contains
38+
//
39+
// fileprivate func myNeatoUtility(int: Int, value: Value, function: Function) { ... }
40+
//
41+
// and
42+
//
43+
// let myNeatoUtilityTest =
44+
// FunctionTest("my_neato_utility") { function, arguments, test in
45+
// // The code here is described in detail below.
46+
// // See 4).
47+
// let count = arguments.takeInt()
48+
// let target = arguments.takeValue()
49+
// let callee = arguments.takeFunction()
50+
// // See 5)
51+
// myNeatoUtility(int: count, value: target, function: callee)
52+
// // See 6)
53+
// print(function)
54+
// }
55+
// 1) A test test/SILOptimizer/interesting_functionality_unit.sil runs the
56+
// TestRunner pass:
57+
// // RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s
58+
// 2) A function in interesting_functionality_unit.sil contains the
59+
// specify_test instruction.
60+
// sil @f : $() -> () {
61+
// ...
62+
// specify_test "my_neato_utility 43 %2 @function[other_fun]"
63+
// ...
64+
// }
65+
// 3) TestRunner finds the FunctionTest instance myNeatoUtilityTest registered
66+
// under the name "my_neato_utility", and calls run() on it, passing the
67+
// passing first the function, last the FunctionTest instance, AND in the
68+
// middle, most importantly a TestArguments instance that contains
69+
//
70+
// (43 : Int, someValue : Value, other_fun : Function)
71+
//
72+
// 4) myNeatoUtilityTest calls takeUInt(), takeValue(), and takeFunction() on
73+
// the test::Arguments instance.
74+
// let count = arguments.takeInt()
75+
// let target = arguments.takeValue()
76+
// let callee = arguments.takeFunction()
77+
// 5) myNeatoUtilityTest calls myNeatoUtility, passing these values along.
78+
// myNeatoUtility(int: count, value: target, function: callee)
79+
// 6) myNeatoUtilityTest then dumps out the current function, which was modified
80+
// in the process.
81+
// print(function)
82+
// 7) The test file test/SILOptimizer/interesting_functionality_unit.sil matches
83+
// the
84+
// expected contents of the modified function:
85+
// // CHECK-LABEL: sil @f
86+
// // CHECK-NOT: function_ref @other_fun
87+
//
88+
//===----------------------------------------------------------------------===//
89+
90+
import Basic
91+
import SILBridging
92+
93+
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) {
98+
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)
106+
}
107+
}
108+
109+
public struct TestArguments {
110+
public var bridged: BridgedTestArguments
111+
fileprivate init(bridged: BridgedTestArguments) {
112+
self.bridged = bridged
113+
}
114+
115+
public var hasUntaken: Bool { bridged.hasUntaken() }
116+
public func takeString() -> StringRef { StringRef(bridged: bridged.takeString()) }
117+
public func takeBool() -> Bool { bridged.takeBool() }
118+
public func takeInt() -> Int { bridged.takeInt() }
119+
public func takeOperand() -> Operand { Operand(bridged: bridged.takeOperand()) }
120+
public func takeValue() -> Value { bridged.takeValue().value }
121+
public func takeInstruction() -> Instruction { bridged.takeInstruction().instruction }
122+
public func takeArgument() -> Argument { bridged.takeArgument().argument }
123+
public func takeBlock() -> BasicBlock { bridged.takeBlock().block }
124+
public func takeFunction() -> Function { bridged.takeFunction().function }
125+
}
126+
127+
extension BridgedTestArguments {
128+
public var native: TestArguments { TestArguments(bridged: self) }
129+
}
130+
131+
public struct TestContext {
132+
public var bridged: BridgedTestContext
133+
fileprivate init(bridged: BridgedTestContext) {
134+
self.bridged = bridged
135+
}
136+
}
137+
138+
extension BridgedTestContext {
139+
public var native: TestContext { TestContext(bridged: self) }
140+
}
141+
142+
private func registerFunctionTest(
143+
_ test: FunctionTest,
144+
implementation: BridgedFunctionTestThunk) {
145+
test.name._withStringRef { ref in
146+
registerFunctionTest(ref, implementation)
147+
}
148+
}
149+
150+
public func registerSILTests() {
151+
registerFunctionTest(parseTestSpecificationTest, implementation: { parseTestSpecificationTest.run($0, $1, $2) })
152+
}
153+
154+
// Arguments:
155+
// - string: list of characters, each of which specifies subsequent arguments
156+
// - A: (block) argument
157+
// - F: function
158+
// - B: block
159+
// - I: instruction
160+
// - V: value
161+
// - O: operand
162+
// - b: boolean
163+
// - u: unsigned
164+
// - s: string
165+
// - ...
166+
// - an argument of the type specified in the initial string
167+
// - ...
168+
// Dumps:
169+
// - for each argument (after the initial string)
170+
// - its type
171+
// - something to identify the instance (mostly this means calling dump)
172+
let parseTestSpecificationTest =
173+
FunctionTest(name: "test_specification_parsing") { function, arguments, context in
174+
struct _Stderr : TextOutputStream {
175+
public init() {}
176+
177+
public mutating func write(_ string: String) {
178+
for c in string.utf8 {
179+
_swift_stdlib_putc_stderr(CInt(c))
180+
}
181+
}
182+
}
183+
var stderr = _Stderr()
184+
let expectedFields = arguments.takeString()
185+
for expectedField in expectedFields.string {
186+
switch expectedField {
187+
case "A":
188+
let argument = arguments.takeArgument()
189+
print("argument:\n\(argument)", to: &stderr)
190+
case "F":
191+
let function = arguments.takeFunction()
192+
print("function: \(function.name)", to: &stderr)
193+
case "B":
194+
let block = arguments.takeBlock()
195+
print("block:\n\(block)", to: &stderr)
196+
case "I":
197+
let instruction = arguments.takeInstruction()
198+
print("instruction: \(instruction)", to: &stderr)
199+
case "V":
200+
let value = arguments.takeValue()
201+
print("value: \(value)", to: &stderr)
202+
case "O":
203+
let operand = arguments.takeOperand()
204+
print("operand: \(operand)", to: &stderr)
205+
case "u":
206+
let u = arguments.takeInt()
207+
print("uint: \(u)", to: &stderr)
208+
case "b":
209+
let b = arguments.takeBool()
210+
print("bool: \(b)", to: &stderr)
211+
case "s":
212+
let s = arguments.takeString()
213+
print("string: \(s)", to: &stderr)
214+
default:
215+
fatalError("unknown field type was expected?!");
216+
}
217+
}
218+
}
219+

include/swift/SIL/SILBridging.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,50 @@ BridgedBasicBlock BridgedArgument::getParent() const {
15221522
return {getArgument()->getParent()};
15231523
}
15241524

1525+
namespace swift::test {
1526+
struct Arguments;
1527+
class FunctionTest;
1528+
} // namespace swift::test
1529+
1530+
struct BridgedTestArguments {
1531+
swift::test::Arguments *_Nonnull arguments;
1532+
1533+
bool hasUntaken() const;
1534+
1535+
SWIFT_IMPORT_UNSAFE
1536+
llvm::StringRef takeString() const;
1537+
1538+
bool takeBool() const;
1539+
1540+
SwiftInt takeInt() const;
1541+
1542+
SWIFT_IMPORT_UNSAFE
1543+
BridgedOperand takeOperand() const;
1544+
1545+
SWIFT_IMPORT_UNSAFE
1546+
BridgedValue takeValue() const;
1547+
1548+
SWIFT_IMPORT_UNSAFE
1549+
BridgedInstruction takeInstruction() const;
1550+
1551+
SWIFT_IMPORT_UNSAFE
1552+
BridgedArgument takeArgument() const;
1553+
1554+
SWIFT_IMPORT_UNSAFE
1555+
BridgedBasicBlock takeBlock() const;
1556+
1557+
SWIFT_IMPORT_UNSAFE
1558+
BridgedFunction takeFunction() const;
1559+
};
1560+
1561+
struct BridgedTestContext {
1562+
swift::SwiftPassInvocation *_Nonnull invocation;
1563+
};
1564+
1565+
typedef void (*_Nonnull BridgedFunctionTestThunk)(BridgedFunction,
1566+
BridgedTestArguments,
1567+
BridgedTestContext);
1568+
void registerFunctionTest(llvm::StringRef, BridgedFunctionTestThunk thunk);
15251569

15261570
SWIFT_END_NULLABILITY_ANNOTATIONS
15271571

0 commit comments

Comments
 (0)