|
| 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 | + |
0 commit comments