Files
swift-mirror/lib/SILOptimizer/UtilityPasses/ConstantEvaluableSubsetChecker.cpp
Ravi Kandhadai 032442da93 [Constant Evaluator] Add a new sil-opt pass to check constant evaluability
of Swift code snippets. Add a new test: constant_evaluable_subset_test.swift
that tests Swift code snippets that are expected to be constant evaluable and
those that are not.
2019-09-16 18:49:19 -07:00

157 lines
5.6 KiB
C++

//===--ConstantEvaluableSubsetChecker.cpp - Test Constant Evaluable Swift--===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//
// This file implements a pass for checking the constant evaluability of Swift
// code snippets. This pass is only used in tests and is not part of the
// compilation pipeline.
#define DEBUG_TYPE "sil-constant-evaluable-subset-checker"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Module.h"
#include "swift/Demangling/Demangle.h"
#include "swift/SIL/CFG.h"
#include "swift/SIL/SILConstants.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/ConstExpr.h"
using namespace swift;
namespace {
static const StringRef constantEvaluableSemanticsAttr = "constant_evaluable";
static const StringRef testDriverSemanticsAttr = "test_driver";
template <typename... T, typename... U>
static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
Diag<T...> diag, U &&... args) {
return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
static std::string demangleSymbolName(StringRef name) {
Demangle::DemangleOptions options;
options.QualifyEntities = false;
return Demangle::demangleSymbolAsString(name, options);
}
/// A SILModule pass that invokes the constant evaluator on all functions in a
/// SILModule with the semantics attribute "test_driver". Each "test_driver"
/// must invoke one or more functions in the module annotated as
/// "constant_evaluable" with constant arguments.
class ConstantEvaluableSubsetChecker : public SILModuleTransform {
llvm::SmallPtrSet<SILFunction *, 4> constantEvaluableFunctions;
llvm::SmallPtrSet<SILFunction *, 4> evaluatedFunctions;
/// Evaluate the body of \c fun with the constant evaluator. \c fun must be
/// annotated as "test_driver" and must invoke one or more functions annotated
/// as "constant_evaluable" with constant arguments. Emit diagnostics if the
/// evaluation of any "constant_evaluable" function called in the body of
/// \c fun fails.
void constantEvaluateDriver(SILFunction *fun) {
ASTContext &astContext = fun->getASTContext();
// Create a step evaluator and run it on the function.
SymbolicValueBumpAllocator allocator;
ConstExprStepEvaluator stepEvaluator(allocator, fun,
/*trackCallees*/ true);
for (auto currI = fun->getEntryBlock()->begin();;) {
auto *inst = &(*currI);
if (isa<ReturnInst>(inst))
break;
auto *applyInst = dyn_cast<ApplyInst>(inst);
SILFunction *callee = nullptr;
if (applyInst) {
callee = applyInst->getReferencedFunctionOrNull();
}
Optional<SILBasicBlock::iterator> nextInstOpt;
Optional<SymbolicValue> errorVal;
if (!applyInst || !callee ||
!callee->hasSemanticsAttr(constantEvaluableSemanticsAttr)) {
std::tie(nextInstOpt, errorVal) =
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
assert(nextInstOpt && "non-constant control flow in the test driver");
currI = nextInstOpt.getValue();
continue;
}
// Here, a function annotated as "constant_evaluable" is called.
llvm::errs() << "@" << demangleSymbolName(callee->getName()) << "\n";
std::tie(nextInstOpt, errorVal) =
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
if (errorVal) {
SourceLoc instLoc = inst->getLoc().getSourceLoc();
diagnose(astContext, instLoc, diag::not_constant_evaluable);
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
}
if (!nextInstOpt) {
break; // This instruction should be the end of the test driver.
}
currI = nextInstOpt.getValue();
}
// Record functions that were called during this evaluation to detect
// whether the test drivers in the SILModule cover all function annotated
// as "constant_evaluable".
for (SILFunction *callee : stepEvaluator.getFuncsCalledDuringEvaluation()) {
evaluatedFunctions.insert(callee);
}
}
void run() override {
SILModule *module = getModule();
assert(module);
for (SILFunction &fun : *module) {
// Record functions annotated as constant evaluable.
if (fun.hasSemanticsAttr(constantEvaluableSemanticsAttr)) {
constantEvaluableFunctions.insert(&fun);
continue;
}
// Evaluate test drivers.
if (!fun.hasSemanticsAttr(testDriverSemanticsAttr))
continue;
constantEvaluateDriver(&fun);
}
// Assert that every function annotated as "constant_evaluable" was convered
// by a test driver.
bool error = false;
for (SILFunction *constEvalFun : constantEvaluableFunctions) {
if (!evaluatedFunctions.count(constEvalFun)) {
llvm::errs() << "Error: function "
<< demangleSymbolName(constEvalFun->getName());
llvm::errs() << " annotated as constant evaluable";
llvm::errs() << " does not have a test driver"
<< "\n";
error = true;
}
}
assert(!error);
}
};
} // end anonymous namespace
SILTransform *swift::createConstantEvaluableSubsetChecker() {
return new ConstantEvaluableSubsetChecker();
}