Files
swift-mirror/include/swift/SIL/Test.h
2024-07-25 13:52:21 -07:00

318 lines
12 KiB
C++

//===---- Test.h - Testing based on specify_test insts -*- C++ ----*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
//
//===----------------------------------------------------------------------===//
//
// TO ADD A NEW TEST, just add a new FunctionTest instance.
// - In the source file containing the functionality you want to test.
// - #include "swift/SIL/Test.h"
// - namespace swift::test {
// static FunctionTest MyNewTest(
// "my_new_test",
// [](auto &function, auto &arguments, auto &test) {
// });
// } // end namespace swift::test
//
//===----------------------------------------------------------------------===//
//
// Provides a mechanism for writing tests against compiler code in the context
// of a function. The goal is to get the same effect as calling a function and
// checking its output.
//
// This is done via the specify_test instruction. Using one or more instances
// of it in your test case's SIL function, you can specify which test (instance
// of FunctionTest) should be run and what arguments should be provided to it.
// For full details of the specify_test instruction's grammar, see SIL.rst.
//
// The test grabs the arguments it expects out of the test::Arguments instance
// it is provided. It calls some function or functions. It then prints out
// interesting results. These results can then be FileCheck'd.
//
// CASE STUDY:
// Here's an example of how it works:
// 0) A source file, NeatUtils.cpp contains
//
// static void myNeatoUtility(unsigned, SILValue, SILFunction *) { ... }
//
// and
//
// static FunctionTest MyNeatoUtilityTest(
// "my_neato_utility",
// [](auto &function, auto &arguments, auto &test) {
// // The code here is described in detail below.
// // See 4).
// auto count = arguments.takeUInt();
// auto target = arguments.takeValue();
// auto callee = arguments.takeFunction();
// // See 5)
// myNeatoUtility(count, target, callee);
// // See 6)
// function.dump();
// });
// 1) A test test/SILOptimizer/interesting_functionality_unit.sil runs the
// TestRunner pass:
// // RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s
// 2) A function in interesting_functionality_unit.sil contains the
// specify_test instruction.
// sil @f : $() -> () {
// ...
// specify_test "my_neato_utility 43 %2 @function[other_fun]"
// ...
// }
// 3) TestRunner finds the FunctionTest instance MyNeatoUtilityTest registered
// under the name "my_neato_utility", and calls ::run() on it, passing first
// the function, last the test::FunctionTest instance, AND in the middle,
// most importantly a test::Arguments instance that contains
//
// (43 : unsigned long, someValue : SILValue, other_fun : SILFunction *)
//
// 4) MyNeatoUtilityTest calls takeUInt(), takeValue(), and takeFunction() on
// the test::Arguments instance.
// auto count = arguments.takeUInt();
// auto target = arguments.takeValue();
// auto callee = arguments.takeFunction();
// WARNING: Don't call more than one of these in a single expression! The
// order of evaluation is implementation defined, but the values
// must be taken out of the arguments in the order they appear in
// the test.
// 5) MyNeatoUtilityTest calls myNeatoUtility, passing these values along.
// myNeatoUtility(count, target, callee);
// 6) MyNeatoUtilityTest then dumps out the current function, which was modified
// in the process.
// function.dump()
// 7) The test file test/SILOptimizer/interesting_functionality_unit.sil matches
// the
// expected contents of the modified function:
// // CHECK-LABEL: sil @f
// // CHECK-NOT: function_ref @other_fun
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SIL_TEST_H
#define SWIFT_SIL_TEST_H
#include "swift/SIL/ParseTestSpecification.h"
#include "swift/SIL/SILBridging.h"
namespace swift {
class SILFunction;
class SILFunctionTransform;
class SILPassManager;
class DominanceAnalysis;
class DominanceInfo;
class DeadEndBlocks;
namespace test {
struct Arguments;
class TestRunner;
/// A test that is run when the corresponding specify_test instruction is
/// processed by the TestRunner pass.
///
/// Tests are instantiated once at program launch. At that time, they are
/// stored in a registry name -> test. When an specify_test instruction
/// naming a particular test is processed, that test is run.
///
/// This is a value type because we have no persistent location in which to
/// store SwiftCompilerSources tests.
class FunctionTest final {
public:
/// Wraps a test lambda.
///
/// There are three arguments in order of priority:
/// - SILFunction & - the function with the specify_test instruction
/// - Arguments & - the resolved list of args specified by the instruction
/// - FunctionTest & - the test being run; used to find less commonly used
/// values such as the results of analyses
using Invocation = void (*)(SILFunction &, Arguments &, FunctionTest &);
using NativeSwiftInvocation = void *;
private:
/// The lambda to be run.
TaggedUnion<Invocation, NativeSwiftInvocation> invocation;
public:
/// Creates a test that will run \p invocation and stores it in the global
/// registry.
///
/// To create a new test, just write
///
/// namespace swift::test {
/// static FunctionTest myTest("my_new_test", [](
/// SILFunction &function, Arguments &arguments, FunctionTest &test){
/// // test code
/// });
/// } // end namespace swift::test
FunctionTest(StringRef name, Invocation invocation);
/// Creates a test that will run \p invocation and stores it in the global
/// registry.
static void createNativeSwiftFunctionTest(StringRef name,
NativeSwiftInvocation invocation);
/// Computes and returns the function's dominance tree.
DominanceInfo *getDominanceInfo();
/// The function's dead-end blocks.
DeadEndBlocks *getDeadEndBlocks();
/// Returns the active pass manager.
SILPassManager *getPassManager();
/// Returns the indicated analysis.
///
/// NOTE: This function can only be called from files that can import
/// SILFunctionTransform--those in SILOptimizer and libraries that
/// depend on it. See `Layering` below for more details.
template <typename Analysis, typename Transform = SILFunctionTransform>
Analysis *getAnalysis();
SILFunctionTransform *getPass();
SwiftPassInvocation *getSwiftPassInvocation();
//===----------------------------------------------------------------------===//
//=== MARK: Implementation Details ===
//===----------------------------------------------------------------------===//
//
// To read, write, and debug function test failures, see above.
//
// The following implementation details have to do with executing these tests
// and handling library layering.
// TestRunner interface:
private:
struct Dependencies;
/// Run the stored \p invocation lambda.
void run(SILFunction &function, Arguments &arguments,
SILFunctionTransform &pass, Dependencies &dependencies);
/// Retrieve the test with named \p name from the global registry.
static FunctionTest get(StringRef name);
/// The instance of the TestRunner pass currently running this test. Only
/// non-null when FunctionTest::run is executing.
SILFunctionTransform *pass;
/// The function which the TestRunner pass is currently processing. Only
/// non-null when FunctionTest::run is executing.
SILFunction *function;
friend class TestRunner;
// Layering:
//
// Dealing with the differences between the SIL and SILOptimizer libraries.
//
// Motivation: 1) Enable writing FunctionTests inline in any source file that
// can import the SIL library's headers.
// 2) Allow tests to access the tools that are visible in the source
// file where they are written.
//
// Two examples:
// A) Tests in the SIL library (or libraries that can import its headers) must
// be able to access the results of analyses, e.g. DominanceInfo.
// B) Tests in the SILOptimizer library (or libraries that can import its
// headers) must be able to access analyses themselves, e.g.
// DominanceAnalysis.
//
// Because analyses aren't part of the SIL library, the code that computes such
// a result can't called from one of it's source files. For example, this
// isn't possible in some SIL library cpp file
//
// test->getPass()->getAnalysis<DominanceAnalysis>()->get(function);
//
// because DominanceAnalysis isn't visible in SIL (it's defined in
// SILOptimizer). Indeed calling anything on getPass() isn't possible because
// SILFunctionTransform isn't visible in SIL (again, defined in SILOptimizer).
//
// This is further exacerbated by the fact that getting an analysis from a pass
// requires instantiating a template function.
//
// There are two consequences:
//
// 1) Dependencies must be provided directly (i.e. not via analyses) to tests
// in the SIL library.
//
// For example ::getDominanceInfo in `struct Dependencies` below.
//
// 2) The code that allows tests in the SILOptimizer library to access analyses
// must not be instantiated when it's imported into the SIL library.
//
// Concretely, the "extra" template argument `typename Transform` on
// getAnalysis and the thunk in the impl:: namespace below.
private:
/// Functions for getting tools that are visible in the SIL library.
///
/// The implementation is provided in the SILOptimizer libary where analyses
/// are visible: TestRunner::FunctionTestDependenciesImpl.
struct Dependencies {
virtual DominanceInfo *getDominanceInfo() = 0;
virtual DeadEndBlocks *getDeadEndBlocks() = 0;
virtual SILPassManager *getPassManager() = 0;
virtual SwiftPassInvocation *getSwiftPassInvocation() = 0;
virtual ~Dependencies(){};
};
/// The vendor for dependencies provided to the test by TestRunner. Only
/// non-null when FunctionTest::run is executing.
Dependencies *dependencies;
public:
/// Creates a test that will run \p invocation. For use by
/// createNativeSwiftFunctionTest.
FunctionTest(StringRef name, NativeSwiftInvocation invocation);
};
/// Thunks for delaying template instantiation.
///
/// Because C++ lacks "runtime concepts" (Swift's protocols), it's not possible
/// for the Dependencies struct to have a method like
///
/// template <typename Analysis>
/// virtual Analysis *getAnalysis() = 0;
///
/// In order to give tests access to analyses, SILFunctionTransform::getAnalysis
/// template must be instantiated by the test code. This could be done by
/// making the test-running pass available directly to test code. Here,
/// instead, it is done by providing a passthrough on test. But this is
/// complicated by library layering:
///
/// Calling methods directly on `FunctionTest::pass` is illegal from the SIL
/// library (or any library that can't import SILOptimizer headers, where
/// SILFunctionTransform is defined):
///
/// member access into incomplete type 'swift::SILFunctionTransform'
///
/// Instead, these thunks are called with the pass as an argument. Because the
/// static type of pass in these thunks is just "pointer to `typename
/// Transform`", it's legal to call methods on the pass here: these templates
/// can only be instantiated in libraries that can import SILOptimizer.
namespace impl {
template <typename Analysis, typename Transform>
Analysis *getAnalysisFromTransform(Transform *pass) {
return pass->template getAnalysis<Analysis>();
}
} // end namespace impl
template <typename Analysis, typename Transform>
Analysis *FunctionTest::getAnalysis() {
return impl::getAnalysisFromTransform<Analysis, Transform>(pass);
}
inline SILFunctionTransform *FunctionTest::getPass() { return pass; }
} // namespace test
} // namespace swift
#endif